When the planner can't see ahead

The GOAP planning strategies had comprehensive unit tests — mock the CaseDefinition, mock the CaseContext, assert the planner returns the right binding. What they didn’t have was a single test exer...

Four Annotation Layers, One Interface

An agent that assesses aircraft defects needs to be cautious and methodical. Its inspector sign-off requires two independent approvals. Every step must produce an audit trail that satisfies EASA Pa...

When "Created At" Means "Deployed At"

CaseInstanceResponse had a createdAt field from day one. It was labelled “Case creation timestamp” in the OpenAPI schema. The value came from CaseMetaModel.getCreatedAt() — which records when the d...

The Store That Forgot to Persist

The orchestration workbench needed an SSE endpoint streaming execution state. The engine already had all the data — ExecutionStateSnapshot composing plan model, DAG plan, and DAG result into a sing...

The View That Wasn't Just a View

The issue said “no new logic — just a new view composing existing snapshots.” Six endpoints already existed on PlanResource serving decomposition trees, DAG plans, execution results, and live plan ...

Two gates before you observe

The engine already observes two compliance dimensions — latency and attestation rate — and feeds them to eidos for trust scoring. Both are straightforward: measure the clock, check the outcome. Add...

The Budget That Wasn't a Number

Cost budgets looked simple at first glance. The issue asked for a costBudget field on PlanningConstraints — an integer, like resourceLimit. One number, one meaning.

When Your Routing Doesn't Route

The plan for engine#882 was straightforward: when an HTN step fails, feed the failure context back to the decomposition strategy and let it produce a revised plan. Three moving parts — ReplanContex...

When Your Timeout Doesn't Actually Time Out

The agentic planning epic has four phases. Phase 1 landed the execution bridge — blocks patterns running inside the engine’s worker boundary. Phase 2 is about giving those patterns resource awarene...

Crash Recovery for Pattern Execution

The agentic pattern infrastructure can now survive a JVM crash mid-execution. Before this, if the process died while a DEBATE or SEQUENCE pattern was running, the engine retried from scratch — ever...

Agents That Grow Their Own Goals

The landscape analysis I did in July surveyed every multi-agent project worth examining — Smallville, Emergence World, Concordia, the lot. One gap stood out from the rest: no project has agents tha...

Plan Adaptation — When Plans Meet Reality

The engine’s goal decomposition creates a plan at case start — but plans go stale the moment a worker returns with unexpected results. Plan adaptation closes that gap: after each worker completion,...

The Right Cache at the Wrong Layer

BGE-M3 runs at 4.5 seconds per entry on CPU. That’s the full ONNX forward pass — dense 1024-dim, sparse learned lexical, ColBERT multi-vector — all from one model. At 2590 garden entries, a full re...

Why Your Agent Forgets Everything

“Memory” in LLM agents has become one of those terms that means whatever the vendor needs it to mean. Most of the time, it means a chat log with summarisation bolted on. LangChain’s memory modules,...

Workspace Symlink Archaeology

The engine’s HANDOFF had work-repo issues in it. #329, #330, #298, #152, #328 — all from casehubio/work, not casehubio/engine. Branch detection was showing a work-repo branch as the active workspac...

A2A and MCP — Handler, Not Provisioner

The langchain4j parity audit flagged two gaps: we couldn’t invoke A2A agents or MCP tools as casehub workers. The obvious implementation path was WorkerProvisioner — it’s the SPI that provisions ex...

When the Spec Drifts and the Code Stays Still

The original epic (#84) was written when Stage was alive — containment hierarchies, parentStageId pointers, CMMN 5.4.4 alignment. A year later, Stage is retired. Compound replaced it. The epic’s vi...

Teaching the Engine Who Should Handle This

Most workflow engines assign human tasks the same way they did twenty years ago: a queue, a round-robin, maybe a role-based filter. The task appears in someone’s inbox. Whether that person is the r...

Two Primitives, One Graph

The unified execution model spec sat for eleven days after its adversarial review. Thirty-five issues raised, twenty-three verified, ten accepted. The spec was solid. The implementation plan existe...

The Invisible Breakage

I set out to fix four small issues on the engine — the kind of cleanup batch where you expect to be done in an hour. Two constructor mismatches from upstream SNAPSHOT changes, one checkstyle violat...

Three Models, One Vocabulary

CaseHub has three coordination models: blackboard (engine), workflow (engine-flow), and agentic patterns (blocks). They all answer the same question — how do I coordinate work across multiple agent...

Every Boundary Is the Same Boundary

CaseHub passes data through five different boundary types — worker input, work items, sub-case context, signals, connectors. Every one of them uses Map<String, Object>. Every one of them has ...

Six Issues, One Branch

The last session landed the universal routing strategy convention (#634) — NamedStrategy, StrategyResolver, five SPIs retrofitted. That work left a trail of follow-on issues: evaluation happening i...

Three Gaps, One Branch

The hybrid orchestration epic shipped last session. What it left behind were three follow-up issues — not defects exactly, more like threads that were cut short when the scope boundary was drawn.

Routing predicates and the art of not leaking

The composite WorkerExecutionManager (#461) solved CDI ambiguity when co-deploying multiple worker backends. But it left a question hanging: what happens when Quartz — the catch-all fallback — catc...

Where Does a Timeout Belong?

OutcomePolicy declared onExpired from the start. The YAML schema parsed it, the mapper wired it, the default constructor set it to REROUTE. But nothing in the engine ever used it. A worker that tim...

The Bug That Documented Itself Wrong

The failure cascade landed in the previous session — WorkerOutcome sealed type, OutcomePolicy, structured _outcomes state, agent exclusion. Four follow-up issues remained open. All four turned out ...

The bridge and the try/catch that lied

The foundation constraint is simple: casehub-qhorus and casehub-work are peer Foundation modules. Neither can depend on the other. But consumer apps often need both — a message arrives on a qhorus ...

The Batch That Paid for Itself

When the issue backlog accumulates fifteen XS/S items, the temptation is to knock them out one by one across separate branches. I tried the opposite — one branch, all fifteen, sequential execution.

The Qualifier Nobody Inherits

Four issues. Two were already done — one we’d fixed in the previous session and never closed the ticket, one adapted to a SNAPSHOT change without noticing the issue still existed on the board. Clos...

The Map Before You Dig

Four small issues in a single batch branch. Three were exactly what they looked like. One had a finding inside.

Wiring workflow steps to the engine

Engine#206 has been on the list for a while: when a Worker runs a Serverless Workflow, the steps inside it have no way to dispatch other casehub workers. The workflow executes in quarkus-flow’s exe...

Four issues, two architectural surprises

The tenancy enforcement batch took longer than the individual issue sizes suggested. Four issues on one branch: a migration (#411), a CDI qualifier pattern (#405), DB-level Row Level Security (#406...

Tenancy Threading Gets Explicit

My first instinct for tenancy in the engine repositories was elegant: inject CurrentPrincipal, read tenancyId() in each query, filter silently. Callers stay clean. Protocol PP-20260520-e6a5f0 says ...

Six handlers and a miss

The batch we cleared this session was five issues — all small, all overdue, all the kind of bugs that pass unit tests and quietly break in production.

Routing the Uncertain

Two routing issues had been sitting on the backlog since the AgentRoutingStrategy SPI shipped. One was about what to do when all candidates are borderline on trust — the four-phase model says escal...

REJECTED is not FAULTED

Four correctness fixes shipped today as a single PR. Three are mechanical — a PlanItem stuck in RUNNING when Quartz retries exhausted, an atomic counter race in the JPA repository, an OTel trace ID...

The Guard That Did Too Much

The signal gaps in the engine — Qhorus human messages that never reach cases, WorkItem escalations that freeze WAITING cases, M-of-N group outcomes that vanish — all traced to eleven lines in CaseC...

Scope and the Silent Guard

Two small fixes that close a larger gap — scope propagation for SLA preference routing and a silent when-field bug in contextChange bindings.

Waiting Is Not Running

The previous branch left the @QuarkusTest suites in casehub-blackboard blocked — RoutingCursorStore unsatisfied dependency. The fix was straightforward: add quarkus.arc.exclude-types=io.casehub.wor...

Clearing the Interim Address

When we moved JQEvaluator into casehub-engine-common as part of the JQ consolidation a few days ago, we knew it was temporary. The comment in CLAUDE.md said so explicitly: follow-on platform extrac...

The Wrapper That Earns Its Keep

Before touching any code I stopped and asked whether the casehub Agent class should exist at all. LangChain4j has its own agent model — UntypedAgent, AgenticScope, supervisor patterns. Was the case...

The Hand-Rolled Parser That Shouldn't Exist

The bug was simple: { humanApproval: { status: .decision } } in an outputMapping was producing a String literal in the case context instead of a nested Map. The fix should have been five lines. It ...

The List That Emptied Itself

The “What’s Next” table in my handover had nine items. I went to start the first one — engine#300, add the deadline field to the COMMAND message — and found the issue closed. The code was already t...

P1D Was Never Invalid

The issue description was confident: “Duration.parse() throws DateTimeParseException for any non-PT-prefixed format (e.g. P1D).” We were adding validation to CaseDefinitionYamlMapper.convertHumanTa...

The Pin Was Two Lines. The CDI Was Not.

work-adapter had a json-schema-validator pin sitting in the wrong place — it belonged in the root pom.xml alongside the other BOM overrides, not buried in the module that happened to need it first....

The Deadline Gets Through

Claudony is about to start tracking Commitments — Qhorus obligation records that bound how long an agent has to act on a COMMAND. To set an expiry, it needs the case budget deadline. The problem: c...

Giving the YAML a Human Concept

The casehub-engine-work-adapter has been implemented for weeks. HumanTaskScheduleHandler creates WorkItems. WorkItemLifecycleAdapter listens for completion and signals the engine to resume. The cod...

Testing the Handler, Not the Bus

Most of the day was cleanup — small things that had accumulated since the atomicity work. A two-arg arity mismatch when casehub-work extended SelectionContext and WorkItemCreateRequest without upda...

Atomic by Design

The bug in HumanTaskScheduleHandler was easy to describe: if WorkItem creation fails after item.markRunning(), the PlanItem ends up stuck RUNNING with no WorkItem to complete it. The engine won’t r...

Honouring the Contract

HumanTaskScheduleHandler has had a stub in template mode for a while. The comment was honest: // Template mode not yet implemented — leave PlanItem PENDING so binding stays eligible. What was less ...

SWF doesn't have a human

The previous entry covered the HumanTask binding design — the sealed BindingTarget, the outbound HumanTaskScheduleHandler, the outputMapping round-trip. Claude had built the implementation in a sep...

Config over CDI

The previous entry ended with a loose end: JsonPatchContextDiffStrategy still using @Alternative while the no-op next door had just moved to @DefaultBean. Before touching it I wanted to understand ...

Nine defaults, one wrong package

Nine SPI no-op defaults annotated with @DefaultBean to eliminate CDI ambiguity errors in consumer repos — plus a ninth bean found lurking in an import list, and a package that doesn't exist in the ...

BindingTarget and the sealed dispatch

Adding HumanTaskTarget as a sealed BindingTarget permit — replacing nullable fields with a type-safe sealed interface so every dispatch path is exhaustive and no new binding type can be introduced ...

The half that was missing

The outbound path from engine binding to WorkItem creation was missing — tracing why WorkerProvisioner couldn't work, and designing HumanTaskScheduleHandler as the correct event-driven hook.

Optional Belongs on a Short Leash

When Optional belongs in an API — tracing Brian Goetz's original intent, identifying where casehub-engine's MapCaseFile.get() fell on the wrong side, and codifying the result as a platform rule.

Going Live — and the Two-Backup Mystery

Pushing the reconstructed engine history live surfaces a silent rebase failure and a detective story about two backup branches, a stale fork, and feature code that never made it to casehubio.

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...

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...

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...

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...

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...

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...

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...

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.

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.

Two CaseHubs, One Design

Discovering a parallel casehub-engine implementation and charting a 9-phase plan to unify both systems into one coherent design.