Blocks has had a summarisation framework for weeks. Qhorus has had a channel summary slot for weeks. Neither talked to the other. The SummaryUpdateHook SPI existed in qhorus-api — with a NoOpSummaryUpdateHook that returned currentSummary unchanged. The plumbing was there. The connector wasn’t.

That’s what #64 is. Not new infrastructure. Not a new framework. Glue.

The SPI gap

The first thing we hit: SummaryUpdateContext tells you how many messages arrived since the last update, but not what they say. A summariser that can’t read messages can’t summarise. The hook needed the actual content.

Two additions to the qhorus-api record: recentMessages (pre-fetched by qhorus — the 90% path) and messageQuery (a channel-scoped query function for custom access patterns like sliding windows or full re-summarisation). The combination means simple hooks just use the messages they’re given, and complex hooks can ask for more.

This is a cross-repo change — qhorus SPI enrichment committed to qhorus main, mvn install, then blocks implements the hook. Same pattern as the oversight and routing consolidations.

Two implementations, one SPI

The design question was: heuristic or LLM? The answer was both, layered with CDI.

HeuristicChannelSummariser is the @DefaultBean. Append-only — participant names, message counts, time spans, topics. Structural signals extracted from message metadata. Zero LLM cost, deterministic, always works. It’s the baseline that every deployment gets.

LlmChannelSummariser is @Alternative @Priority(1). Edit-mode by default — it rewrites the entire summary when new messages change the picture. A discussion that was unresolved becomes a decision. A tentative plan becomes confirmed. The LLM naturally integrates new information into the existing narrative rather than appending a delta.

The append-vs-edit distinction matters. Append-only summaries accumulate redundancy — the same topic discussed across multiple windows gets mentioned in each delta separately. Edit mode produces a single coherent summary at the cost of an LLM call. The SummaryMode enum makes this a configuration choice.

What I didn’t build

I didn’t wire this through the streaming pipeline (SummarisationRunner, EventAccumulator, windowing). The hook is pull-based — qhorus calls it when a threshold is crossed, passes a batch, expects a string back. The pipeline is push-based — continuous event flow with windowed accumulation. Different integration patterns for different use cases.

The hook uses Summariser (the functional interface) directly. No pipeline machinery. The hook IS the tick — one invocation, one summarisation, one result.


<
Previous Post
The Reactive Layer That Was Already Dead
>
Next Post
When Themes Became Programs