Skip to content

Feature/collab topic thread context - #3284

Open
ibrahimmahaba wants to merge 10 commits into
devfrom
feature/collab-topic-thread-context
Open

ibrahimmahaba wants to merge 10 commits into
devfrom
feature/collab-topic-thread-context

Conversation

@ibrahimmahaba

Copy link
Copy Markdown
Contributor

Topic chats: find facts that only appear in the body of a topic's email threads

Problem

SearchMail only matched a thread's subject, summary and people. In a topic chat, a fact that appears
only in a message body (a date, a code, a decision) could not be found, and the assistant answered
"not found" or guessed from unrelated threads. Chats that belong to a topic also searched all of the
owner's mail instead of the topic.

What this changes

In a topic chat, SearchMail now reads the message text of the topic's own threads and returns the
passage that answers the question, with a quote, who wrote it, the date and a confidence.

  1. Topic default. A chat with linked topics searches those topics by default. allTopics opts out;
    message text is then not searched.
  2. Question kept whole. The owner's question is used as asked (trimmed, one line, max 500 chars).
    No words are split or dropped.
  3. Read the whole topic. All messages of the topic's conversations are listed from the mailbox,
    including ones that were never imported (Graph $batch, 10 conversations per call, paged).
  4. Rules first. Muted/hidden threads, excluded and never-ingest senders and folders are filtered
    before any model sees a message.
  5. Classifier scores every message. COLLAB_CLASSIFIER_ENGINE_ID (a Jev / ITypeSafeEngine model)
    answers "does this message itself answer the question?" as a probability, in parallel.
  6. Text model quotes the likeliest. COLLAB_LLM_ENGINE_ID reads the top 10 in batches of 4 (parallel) and
    returns relevant / confidence / quote / reason. A quote that is not in the message text is discarded.
  7. One best message per thread: by judge confidence, then classifier score.
  8. The result carries bodySearch.stats (mode, counts, classifiedTop, timings) and a note.

Also: topic briefs now show the thread count, the RelatedThreads agent tool is added, and the prompt tells
the agent to pass the question as asked, answer only from quoted text, and not compute dates itself.

Fallbacks

Failure Behaviour
No classifier, no permission, or not a Jev model Judged search with the text model
Classifier path fails (mailbox, all scores fail, timeout) Judged search; reason in the note
Some classifier calls fail Others used; status partial
Some conversations cannot be listed in Graph Counted in stats.conversationsUnread; partial. If none can be read, falls back
One or more judge batches fail Others used. If all fail, likeliest messages are shown unquoted with classifier confidence, and the note says why
Judged search fails or no text model Word-based body search (old behaviour of this feature), noted as "matched by words only"
Everything fails Status unavailable; the metadata results are still returned

SemossPixelException (login/permission) is always rethrown, never swallowed.

Configuration (RDF_Map.prop, all optional)

Setting Default Range Meaning
COLLAB_TOPIC_SEARCH_MAX_MESSAGES 200 1-2000 Messages scored per question, newest first
COLLAB_TOPIC_SEARCH_PER_CONVERSATION 25 1-500 Messages read per conversation (paged)
COLLAB_TOPIC_SEARCH_THREADS 6 1-32 Parallel classifier/judge calls (read at startup)

Unset or non-numeric values use the default; out-of-range values are clamped.

Privacy and safety

  • Only the owner's topic threads are read, and only after the same privacy check as before.
  • Excluded and never-ingest content never reaches a model or the output.
  • Model calls run on their own pool threads with their own Insight, so raw model JSON is never logged
    into the owner's chat room or the agent's context.
  • Graph $filter values are quote-escaped.

Compatibility

  • searchAll and non-topic search behave as before. The wider question-word list is applied only to the
    body search words, not to the subject/summary/people match.
  • BrainMessageSource.conversationMessages is a new default method (returns null), so other sources are
    unaffected. The fixture source (COLLABORATION_FIXTURE_MAIL) implements it for offline testing.
  • No schema changes.

Files

11 files, about +2,176 / -36. Main changes:

  • BrainTopicJudge (new): classifier + text-model search, fallbacks, settings
  • BrainTopicBodySearch (new): word-based fallback and shared context/gate/result types
  • BrainThreadFinder: multi-topic search, body-search integration, QUESTION_WORDS
  • BrainGraphMessageSource, BrainFixtureMessageSource, BrainMessageSource: conversationMessages
  • BrainSearchThreadsReactor: topic default, allTopics, parameter description
  • CollaborationPrompts, CollaborationAgentTools, BrainTopicBrief, BrainGetRelatedItemsReactor

How it was tested

Manually in the local Tomcat + UI against a fixture mailbox (~440 messages), with a topic of 40 messages whose
fact appears only in a message body and contradicts other threads. Question:
"What is the new date for the order-routing cutover".

  • Found the answer in one SearchMail call, quoted the right message, and the agent answered from it.
  • classify about 4s for 40 messages, judge about 5s, about 10s total.
  • Not covered: no JUnit tests (by agreement), real Graph $batch and paging with bodies, topics larger
    than the 200-message cap, and the never-ingest sender test on a real mailbox.

Known limitations

  • Only the newest COLLAB_TOPIC_SEARCH_MAX_MESSAGES messages of a topic are classified.
  • Graph does not guarantee message order inside a conversation when a conversationId filter is used, so
    the per-conversation limit is applied to an unspecified subset when a conversation is longer.
  • The Jev classifier separates relevant from irrelevant messages only weakly; the text model makes the
    final decision.
  • Messages in conversations not yet linked to the topic are not searched.

Reviewer notes

  • Needs H2 2.x databases (dev already requires this).
  • Please verify on a real mailbox: a conversation of more than 25 messages, and a topic of more than 200.

@ibrahimmahaba
ibrahimmahaba requested a review from a team as a code owner November 6, 2026 14:27
@snyk-io

snyk-io Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

ibrahimmahaba and others added 10 commits November 6, 2026 16:29
SearchMail inside topics now also searches message text through the mailbox search, scoped to the topic's conversations, with the rules gate applied to every match. Topic chats default SearchMail to the chat's topics, the topic brief states the thread count, and BrainGetRelatedItems is an agent tool (RelatedThreads).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…g words only

The topic search reads candidate messages (mailbox hits, with related words when none holds every word, plus the newest) and a model picks the passages that answer the question, each with a quote that is verified against the message. Falls back to word matching without a model. Results report timings and counts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The classifier is a Jev (TypeSafe) model that only answers typed questions, so the free-form relevance judge now uses the text model and skips TypeSafe engines. A failed judge reports its reason in the search note.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Question and filler words no longer become search words, the mailbox hits are ranked by subject and body preview before the read cap, a short screening call picks the likeliest messages for the full judge, the judge is stricter, and the assistant is told not to work out dates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Model calls are logged against the room of the calling thread, so judge responses showed up in the owner's chat and in the assistant's own context. Run them on a pool thread, and report which messages the judge saw.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…n as asked

The classifier scores each message of the topic's conversations against the
unsplit question; the text model quotes the likeliest. Falls back to the
judged and word searches.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e failed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… prefer the higher score

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- keep the wider question-word list to the body search so subject, summary
  and people matching behaves as before
- count conversations Graph could not list and report a partial search, or
  fail over to the fallbacks when none could be read
- do not swallow login errors when resolving the chat's topics, and log
  other failures

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…n paging

COLLAB_TOPIC_SEARCH_MAX_MESSAGES (200), COLLAB_TOPIC_SEARCH_PER_CONVERSATION
(25) and COLLAB_TOPIC_SEARCH_THREADS (6) in RDF_Map.prop; defaults apply when
unset or not a number, and values are kept within a safe range. Graph
conversations are followed page by page up to the per-conversation limit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ibrahimmahaba
ibrahimmahaba force-pushed the feature/collab-topic-thread-context branch from a7b2770 to f99ca74 Compare November 6, 2026 14:38
@ibrahimmahaba ibrahimmahaba linked an issue Nov 6, 2026 that may be closed by this pull request
5 tasks
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.

[COLLABORATION] The topic's email threads as live context in topic chats

1 participant