Notes | CaseHub - Qhorus
Slack threads and the cache write problem
The Slack channel backend is shipped. #261 is done. But getting there involved
two decisions about timing that weren’t in the spec.
The fix that missed main
CI on casehub was red. The error was a compilation failure in QhorusLedgerEntryRepository and ReactiveLedgerEntryJpaRepository — both importing ActorIdentityProvider from io.casehub.ledger.runtime....
Three Problems, One Queue
The A2A SSE endpoint had what looked like three separate problems: no keepalive (proxies drop idle connections), orphaned consumers (disconnected clients leaving their entries in the registry forev...
When Advisory Makes Orphans
The plan coming in was to do three issues in sequence — #266, #271, #261. The first one dissolved before we started: #266 tracked a MessageReceivedEvent consumer migration that turned out to alread...
Ten issues, one stream
Ten issues closed in one batch. Most were mechanical — annotation fixes, null guards, one docs update, one method addition. But three of them kept throwing surprises, and one of those surprises tur...
Nine Fixes, Two Surprises
Nine S/XS fixes in a single batch branch. Most were mechanical. Two were not.
The Observation That Couldn't Carry Content
The issue spec said to post an EVENT. That was the starting point, and it was wrong in two directions simultaneously.
The Row That Wouldn't Lock
The plan was tidy. Move sequence allocation out of LedgerWriteService.record() —
where it was a racy SELECT-then-persist — and into save(), where it would use
the same MERGE SQL that casehub-ledger...
When the Scheduler Has No Principal
The design for multi-tenancy looked straightforward: JPA stores inject CurrentPrincipal, read tenancyId(), add AND tenancyId = ? to every query. Every read returns only what belongs to the caller’s...
Names that mean something
Channel names in Qhorus have always been a gentleman’s agreement. The database had a uniqueness constraint, nothing more. You could name a channel "Billing Output", "xp9qr", or a UUID string, and t...
A Denial Is Not a Restriction
The oversight channel in Claudony’s three-channel layout was supposed to be the governance channel — the place where agents pause and ask humans for approval before doing something consequential. T...
The Fold
DraftHouse had a local file-based parser for deriving a review summary from
channel history. Claudony wants a digest panel from the work channel. The
pattern is always the same: read the messages, ...
First Contact
Before this branch, ConnectorChannelBackend had one answer for an unrecognised
sender: discard with a WARN. That was fine when channels were pre-provisioned.
It stops being fine the moment you want...
A string is not a contract
Two small issues today, but the fix for one of them turned into a reminder about
what “small” actually means.
Clearing the Queue: Eleven Issues and One Narayana Surprise
The sprint list going into this session was eleven issues — #213 through a batch
of S and XS items that had been accumulating while the connector backend work ran.
I brought Claude in to work throu...
Humans in the mesh
Qhorus has always been an agent-to-agent communication layer. COMMAND, RESPONSE, DONE — speech acts between agents, with the human in the loop only through Claudony’s browser panel. This branch cha...
The reactive gate finally has teeth
ReactiveMessageService.dispatch() has existed for a while with a sign above it
saying “full enforcement coming in #193.” That day arrived today.
Five small fixes and three wrong assumptions
The issues were labelled XS and S. Five of them, all supposedly straightforward.
I worked through each one, and three of them had a wrong assumption embedded in
the design I’d sketched before touch...
The subdirectory that wasn't scoped
The platform protocol for Flyway migrations said: if your module has its own named datasource, place migrations in db/migration/<module>/ and configure quarkus.flyway.<datasource>.locat...
The obligor isn't who sent the command
Small fix today, but one that required a small mental reset.
The @BuildStep that wasn't
Two things got fixed today. One took three lines. The other consumed most of the session and ended with us back where we started — but one concept richer.
What drop-and-create hides
The cleanup epic had six issues. Most were straightforward. Two were not.
The Mesh Beneath the Event
Started with two blockers for clinical: qhorus#154 (correlationId in InboundHumanMessage) and qhorus#153 (CDI event on message receipt). Simple issues. Neither was.
What ExcludedTypeBuildItem Actually Excludes
I went into this confident. The plan was to make quarkus-hibernate-reactive-panache
optional in the runtime pom — so consumers on JDBC-only datasources wouldn’t get the
reactive extension activated...
Making ActorType Explicit
A2A's bare 'agent' role string was silently classified as HUMAN in the ledger — a two-line fix with significant forensics impact, and the harder design question of how to distinguish identity from ...
The Coherence Invariant
A channel can have at most one HumanParticipatingChannelBackend — enforced as a hard constraint at registration — because two human participants on different platforms produce two independent conve...
The Body of Work
Reading normative-layer.md, agent-mesh-framework.md, and work-and-workitems.md together confirms vertical coherence — each layer addresses a distinct concern, with SUSPENDED and sub-delegation corr...
Scoped Trust
Every Qhorus attestation was landing on the global "*" capability tag, collapsing capability-scoped Beta distributions into one number — a two-method fix routes attestations to the correct scope by...
Docs That Build Themselves
Writing the developer guide reveals a missing tool — get_obligation_activity — because Part 6 keeps circling the same question: how do you see everything one obligation touched across all three cha...
Two Bugs That Looked Wrong
Both failing tests present misleading symptoms — the watchdog test counts two alerts because the scheduler runs on a separate thread that can see committed test data, not because evaluateAll() was ...
Platform-Wide Breaking Window
A casehub-engine release window triggers twenty-one tasks across five repos in one session — breaking MCP renames, module splits to stop JPA entities leaking onto test classpaths, and a naming audi...
What the Channel Allows
The allowed_types field enforces NormativeChannelLayout at the infrastructure level, and a production bug hiding behind test infrastructure surfaces — a @TestTransaction isolation gap was letting t...
Six Ways to Query an Obligation
Six new query tools give the normative ledger its first analytical surface — get_obligation_chain computes participant order and handoff count that cannot be extracted from a single list_ledger_ent...
The Normative Ledger Ships — and It Turned Out to Be More Than Logging
Extending the ledger to all nine speech-act types reframes Qhorus from audit middleware to normative governance infrastructure — and the trust scoring models in quarkus-ledger turn out to be compos...
CommitmentStore Ships
PendingReply was always instantiating Singh's social commitment model for the QUERY→RESPONSE case — CommitmentStore completes the picture with seven states covering every path a QUERY or COMMAND ca...
Jlama Fixed, CI Housekeeping
Fixing Jlama takes four cascading commits — the ARM_128 UnsupportedOperationException surfaces only after the boot crash is fixed, and requires the native Apple Silicon library rather than the Java...
Speech-Act Taxonomy Ships
Four research passes through Austin, Searle, FIPA, Singh, and Governatori ground the nine-type taxonomy in a formal obligation lifecycle — each type creates, discharges, or transfers exactly one ki...
Named Datasources and a Rogue Agent
Migrating to a named datasource exposes an inheritance constraint — AgentMessageLedgerEntry and LedgerEntry must share a persistence unit — resolved by including the ledger package in Qhorus's pers...
The Reactive Dual-Stack Ships
The 39 MCP tools split into 20 pure reactive chains and 19 @Blocking delegates, with the QhorusMcpToolsBase extraction fixing a Java import limitation where inherited nested types cannot be importe...
The Reactive Store Layer Ships
Issue #74 ships five reactive store interfaces and implementations via the subagent workflow — one issue per session after the planning tool hit the 32k output limit generating the full eight-issue...
Ledger Adaptation and the Dual-Stack Decision
quarkus-ledger ships dual blocking/reactive repositories, unlocking Qhorus's reactive migration — and AgentMessageLedgerEntryRepository switches from Panache statics to EntityManager injection to r...
Phase 13: DB Independence and the Reactive Question
Phase 13 extracts five store interfaces across all Qhorus domains, with in-memory alternatives in a testing module and Query value objects whose matches() predicate applies the same filter logic as...
Ledger Reconciliation and a Transaction Boundary Fix
Reconciling Qhorus with quarkus-ledger's API changes reveals that ledger write failures should use @Transactional(REQUIRES_NEW) to prevent audit failures from rolling back message delivery.
Phase 12, Error Handling, and the Claudony Blocker
The 39 MCP tools get a principled error handling split — @WrapBusinessError for structured-return tools, error-prefixed strings for text-return tools — and the Claudony blocker is a missing Jandex ...
Phase 12: Structured Observability and an Unplanned Detour
Phase 12 ships structured observability via AgentMessageLedgerEntry, but the unplanned work is discovering that Claudony's hand-rolled JSON-RPC endpoint conflicts with quarkus-mcp-server-http at th...
Phases 1–5: Building the Foundation
Zero Java to five complete phases in one session — BARRIER channel semantics, wait_for_reply with SSE keepalives, and artefact lifecycle — with code review catching a double-claim race and a UUID f...
Locking Down the Mesh
Write permissions, admin role, rate limiting, and observer mode land via strict TDD, with non-@Tool convenience overloads solving the AddParameter-breaks-existing-tests problem without duplicating ...
From Addressing to Human Control
Forty-two commits close phases 6 through 10 — read-side dispatch filtering handles all three target modes in one private method, and human-in-the-loop approval gates move to quarkus-workitems as th...
Day Zero: Designing a Multi-Agent Mesh
Designing the Qhorus multi-agent mesh starts by studying A2A, ACP, AutoGen, LangGraph, and CrewAI — and deciding that Qhorus should be infrastructure orthogonal to all of them, not another orchestr...