CaseHub — All Notes
The Proxy and the Bean
A static ThreadLocal on an @ApplicationScoped bean sidesteps @RequestScoped's dependency on an active HTTP context, making @ProvenanceCapture work in scheduled jobs and @QuarkusTest without a runti...
Cleaning House Before the Merge
Applying the squash policy to two live PRs collapses docs follow-on commits and surfaces a hitchhiker commit that snuck in under a different author's PR.
What Kind of Message Is This?
Adding MessageType to CaseChannelProvider.postToChannel forces a dependency graph decision: the engine takes a narrow compile dependency on casehub-qhorus-api rather than inventing a parallel vocab...
Two Models for Trust
Dimension scores are continuous magnitudes, not binary verdicts, so they need a decay-weighted average rather than a Beta accumulator — passing AttestationVerdict.SOUND forces pure time decay and d...
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...
Server-sent events and two silent failures
Both SSE failures return HTTP 200 with valid-looking data — one because Quarkus adds the data: prefix Claudony was also adding, and one because a static ObjectMapper has no JavaTimeModule and silen...
Speech acts, priority names, and a Panache coupling
NormativeResolution maps GitHub and Jira close events to speech-act semantics, CRITICAL gets renamed URGENT after finding it borrows ITSM severity vocabulary into a task scheduling context where it...
When the Papers Disagree
Three peer-reviewed papers reach three incompatible conclusions on global trust aggregation — the right answer is a pluggable GlobalScoreStrategy SPI rather than picking one framework's position.
Platform Positioning and Incremental Build
Scoring ten use-case candidates across separate market-fit and community-fit tables reveals AML as the strongest entry point — Java is banking, banking is compliance infrastructure, and developers ...
The default that bit us, and why defaults matter
Three unrelated CI failures arrive at once — a Friday business-hours assertion, a SNAPSHOT API drift, and a multi-instance default whose effects only appear under concurrent load — all sharing the ...
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...
Delegates, transactions, and not building a parallel universe
castTo(PgConnection.class) fails immediately because Mutiny codegen produces sibling hierarchies, not parent-child — the subscription needs the raw Vert.x delegate to call notificationHandler().
Shipped: the case panel. Found: a fifty-tool cliff.
The case context panel ships after three upstream API migrations, but partway through a silent tool-count cap of 50 causes send_input to disappear without any warning from the MCP server.
Workers can finally talk back
The CaseChannelProvider.postToChannel path existed but was never connected — WorkerContext.channels was always empty, and the buildContext() result was silently discarded before workers were schedu...
What Polling Was Hiding
Switching from polling to server-initiated push exposes three guarantees polling provided for free — restart resilience, catch-up, and identity — that each require deliberate design in a push model.
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 ...
Three bugs hiding behind the wrong error
The BackPressureFailure filling the CI log is noise — the actual failure is an OptimisticLockException buried further down, caused by the M-of-N coordinator racing against itself during concurrent ...
The Sentinel, This Time
For capabilityTag, the sentinel "*" is the right answer where NULL was right for scope_key — query patterns use the same operator as any scoped query, and every major policy system already uses "*"...
CI Chain Repair
The cross-repo dispatch never fired because GITHUB_TOKEN is scoped to the repo where the workflow runs — six repos needed a PAT, and the CI UI was showing success for failed steps because continue-...
The Workaround That Wasn't
We submitted a PR to fix a null output bug in sdk-java. The maintainer closed it: it wasn't a bug.
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...
Three audit findings and a wrong mental model
Three audit findings include a String.format JSON builder that silently corrupts output on non-trivial actor IDs, a bug the auditor described with the wrong mechanism, and a Bayesian mental model e...
Distributed SSE and the infrastructure tax
Redis for distributed SSE fan-out is rejected because it would force two new infrastructure pieces on users who have neither Redis nor MongoDB — a pluggable WorkItemEventBroadcaster SPI defers the ...
Phase 15: Auditing the Audit Trail
An external audit finds two production bugs in ClaudonyLedgerEventCapture: a swallowed persistence exception that silently drops ledger entries, and a MAX() sequence query with no UNIQUE constraint...
What the Reviews Missed
Five deferred issues ship in one push, but the session's main discovery is that a sentinel empty-string for global scope was unnecessary — the previous session already solved the uniqueness constra...
Phase 14: The Panel Knows Its Case
Adding caseId and roleName to the immutable Session record touches 20+ construction sites, and the left panel auto-expands with clickable worker rows that reconnect the WebSocket without reloading ...
Migration Gaps Closed
Idempotency window, DLQ replay, and SubCaseBinding close the last migration gaps, but a transitive quarkus-ledger dependency bundles JPA entities into every module that touches engine and breaks fo...
M-of-N parallel WorkItems and a rule that needed rewriting
M-of-N parallel WorkItems ships after rewriting LAYERING.md — the rule "if it touches another WorkItem it's orchestration" was already broken by cascade cancellation, so the real test is whether it...
Worker registration as a speech act
WorkerRegistry becomes the single source of truth for all three worker entry paths — static, provisioned, self-registering — and Java 21 sealed classes provide the execution fork without an explici...
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...
Optional by design, and a PostgreSQL test that told the truth
The PostgreSQL Testcontainer test finds a HQL date-truncation query that passes on H2 but fails on PostgreSQL — catching a dialect incompatibility that would have reached production undetected.
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...
Connecting quarkus-work to the blackboard
CaseLifecycleEvent gets its missing worker execution call sites, and the casehub-work-adapter uses CDI choreography to route quarkus-work terminal states into PlanItem transitions via the callerRef...
Wiring the SPIs and adding lifecycle control
The Worker Provisioner SPIs get their call sites wired in, and resume adds a single CONTEXT_CHANGED publish so bindings re-evaluate immediately when a suspended case returns to RUNNING.
SLAs, signals, and a connector library
Business-hours SLA and outbound notifications ship, and a notification routing detail that starts as a one-line decision grows into casehub-connectors — a separate repository born from the question...
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...
The name problem hiding an identity problem
CaseHub uses worker roles while Claudony tracks session UUIDs — WorkerSessionMapping bridges the two identity systems, with a documented MVP limitation when concurrent same-role workers share a case.
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...
The primitive and the orchestrator
A data-driven spawn rule table is rejected before a line of code — the right answer is that subprocess orchestration belongs to CaseHub, and quarkus-work should expose the primitive that CaseHub re...
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...
The config mapping trap
The four CaseHub SPIs wire up cleanly, but @ConfigMapping strict validation fires before library roots register and CaseLedgerEntryRepository turns out not to be a CDI bean despite looking exactly ...
The Fix That Would Have Broken Everything
A plausible commit hardcodes @PersistenceUnit("qhorus") into a generic extension library — caught in git log review before it shipped, with the correct fix being a DefaultBean producer pattern in t...
WorkBroker: wiring the shared SPI into casehub-engine
Wiring WorkBroker into casehub-engine reveals two undocumented quarkus-work-core behaviours — WorkerCandidate.of() silently drops all selections, and WorkItemStatus.EXPIRED.isTerminal() returns false.
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...
Routing Signals, a Health Check, and the Claude That Went Off-Script
Using distinct CDI event record types — full payload, delta, computed-at timestamp — as a strategy selector lets consumers receive only the trust score signals they need without a configuration enum.
A rule, not a preference
Migrating Qhorus to a named datasource hardens into an ecosystem-wide rule — every library owns a named datasource matching its artifact ID — and exposes two direct EntityManager calls hiding behin...
traceId, Entity Listeners, and a Gap I Shouldn't Have Left
Renaming correlationId to traceId fixes a naming collision with established messaging terminology, and a CDI entity listener wires automatic OTel trace ID population at @PrePersist time.
Trust Without Memory
W3C PROV-DM, NIST AI standards, and multi-agent framework research converge on the same answer for LLM agent identity: a stable dereferenceable URI bound to the system configuration, not the session.
The substrate and what grows on it
Splitting quarkus-work-api into pure contract and quarkus-work-core with CDI implementations reveals that WorkBroker and WorkItems are specialisations of a shared Work substrate that CaseHub also n...
casehub-resilience: conflict resolution, a timeout enforcer, and a Vert.x surprise
Building ConflictResolver and CaseTimeoutEnforcer surfaces a Vert.x event bus gotcha: publishing from a non-event-loop thread requires explicit context acquisition, or messages silently disappear.
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...
When the Paper Is Wrong
Implementing EigenTrust transitivity reveals that the original paper's trust matrix normalisation produces incorrect results for pre-trusted peers — the fix derives from first principles rather tha...
Documentation That Lies
Rating each capability for enterprise applicability forces an honest assessment — EigenTrust transitivity earns two stars in 2026 not because it is wrong but because enterprise AI agent meshes have...
Forgiveness Was a Patch
The ForgivenessParams mechanism gets replaced entirely when Bayesian Beta replaces the coarse-grained scoring model — a principled prior that treats uncertainty correctly makes the forgiveness patc...
Ecosystem Mapping, PRs F-J, ADR-0002
Gap closure via PRs F through J expands into a fifteen-issue ecosystem map, and the binding-gating decision — presence of stage.addBinding() as the opt-in — is validated as the only bounded and fai...
Closing Every Gap: Parity, Kogito, and ADR-0002
Four genuine gaps against the prior implementation — including strict PlanItem lifecycle and a CDI pre-registration SPI — are closed via PRs F-G and documented in ADR-0002.
Idle is not one thing
Three CDI-pluggable expiry policies distinguish an idle shell from an autonomously working agent, collected at startup via @Any Instance so new policies are just new beans.
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...
QE Pass: 68 Tests to 99, Five PRs
A systematic comparison against the original implementation surfaces thread-safety gaps in PlanItem and Stage, missing end-to-end scenarios, and a null pointer hidden by the String constructor over...
The filter that grew into a contract
A confidence-gated routing rule that could be wired in twenty lines becomes quarkus-work-filter-registry instead — because inline logic is a trap that becomes load-bearing and unremovable.
No Panache in the Model
Stripping Panache from all extension entities removes a framework dependency consumers didn't choose, and @NamedQuery replaces the static Panache shorthand with startup-validated queries that fail ...
A Clean Entity
Converting LedgerEntry from PanacheEntityBase to a plain @Entity removes the Panache dependency that was blocking Qhorus's reactive migration and unblocks any future consumer choosing a different p...
Catching the Docs Before They Hit a Consumer
The Merkle sprint deleted LedgerHashChain without updating the integration guide, leaving consumers with compile-error-inducing sample code that pointed nowhere — a systematic cleanup removes every...
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...
The observer becomes a participant
A fixed send dock in all three mesh views lets humans post into any active channel without switching panels, turning the read-only mesh observer into an active participant.
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...
Blackboard: Research, Analysis, and Implementation
Academic research into LLM-based blackboard architectures shapes one critical design decision — changing LoopControl.select() to return Uni — before the blackboard module ships its first implementa...
Teaching the Ledger to Speak W3C
LedgerEntry exports as W3C PROV-JSON-LD per subject, deduplicating agents across entries and emitting both sequential and cross-subject wasDerivedFrom edges so regulators get a complete provenance ...
From O(N) to O(log N)
The Merkle Mountain Range structure from RFC 9162 provides O(log N) frontier storage and tamper-evident inclusion proofs signed with Ed25519, making verification genuinely independent of the operator.
Fourteen weeks of foundation, three hours to embed
Qhorus embeds into Claudony after a major Quarkus upgrade rewrites the WebAuthn layer and exposes that old credential files stored aaguid without dashes, silently locking users out.
Cutting the JPA Wire
PR3 strips JPA from the engine module entirely — three domain objects become plain Java, twelve handlers route through three SPI interfaces, and no framework annotation remains in casehub-core.
Aligning with the SDK — storage, expressions, and a hidden test class
Studying the SWF SDK storage abstraction confirms that WorkItemRepository needs multi-field inbox queries — Map doesn't express them cleanly — so a richer query object replaces a direct SWF port.
Two Fields in the Wrong Place
A research pass across ten directions produces an ADR grounding the forgiveness mechanism in two parameters — recency and frequency — after finding that severity double-counts a signal already pres...
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.
The Dedup Wasn't Broken. The Test Was.
PR 2 shipped, Flyway is gone, and two CI failures that turned out to be different problems than they appeared.
Persistence PR 1: Container Chaos and a Quarkus Context Trick
PR 1 of the persistence decoupling plan exposes a Podman misconfiguration and a Quarkus Vert.x context requirement that blocks standard JUnit methods from calling reactive Panache.
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...
Fleet Phase 2: the fleet you can see
Fleet Phase 2 adds the fleet sidebar and WebSocket proxy bridge, and the first Playwright test immediately finds a four-month-old bug hiding in displayName() that no one had noticed.
Examples that prove it
Four runnable examples — expense approval, regulated credit decision, AI content moderation, and document review — together exercise every ledger capability, and the agent: prefix convention derive...
Extracting a Shared Audit Ledger for the Quarkus AI Ecosystem
Qhorus needing Tarkus's audit patterns prompts extraction into a shared library — LedgerEntry uses JPA JOINED inheritance so each domain adds its own subclass table without touching the base schema.
Phase 2: Resilience, Diff Provenance, and a Persistence Rethink
Three new modules designed and shipped — resilience, EventLog enrichment, and a persistence decoupling spec — plus a conversation that changed the ORM approach entirely.
The fleet was inevitable
Fleet Phase 1 extends Claudony to manage sessions across multiple instances — and clears three deferred embarrassments: a hardcoded encryption key committed to the repo, a 30-minute session timeout...
The accountability layer — complexity must not leak
The ledger becomes a CDI observer on WorkItemLifecycleEvent — invisible when absent, zero overhead, zero tables — with hash chain and decision context on by default for GDPR Article 22 compliance.
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...
Phase 2: Standards, a Hidden Bug, and casehub-blackboard
Naming decisions resolved, CaseStatus aligned with CNCF standards, a silent bug found by the tests we wrote, and casehub-blackboard went from brainstorm to 390 tests in one session.
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 ...
Building WorkItems.From Scaffold to Native in One Session
Six phases of quarkus-work ship in one session — the WS-HumanTask research expands the WorkItem model to 27 fields before a line of code is written, and TDD catches two real bugs in Phase 2.
Phase 1: Into casehub-engine
The merge direction reversed before a single line of code was written. casehub-engine becomes the home; Phase 1 lays the extension points incrementally, one PR at a time.
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...
Claudony — The Name That Fits
RemoteCC was always a placeholder — Claudony emerged from the colony metaphor and brought a bioluminescent visual language that makes the architecture literal.
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...
Two CaseHubs, One Design
Discovering a parallel casehub-engine implementation and charting a 9-phase plan to unify both systems into one coherent design.
Session 3: Getting the Architecture Right
Collapsing the lineage graph, redesigning the persistence layer, and researching goal models across BDI, GOAP, CMMN, and HTN.
The Architecture Behind CaseHub: Blackboard Meets CMMN
Two patterns from very different traditions — Blackboard Architecture and CMMN — and why they belong together for agentic AI.
Wanted a Sketch, Got a Framework
One session, 73 files, 14,003 lines of code — what started as a request for a sketch became a working framework.