Skip to content

Commit 0d7eb6f

Browse files
authored
feat(core): pause/resume protocol with ExecutionPhase lifecycle (#80)
Structured pause/resume protocol – workflows suspend mid-pipeline (e.g. human review) and resume without re-executing the agent call. - ExecutionPhase (Initial/Awaiting/Terminal) tracks suspension point, persisted as JSONB phase column - ProcessorOutcome.SuspendForExternal signals pause from post-processors - ResumeInput (ApplyReview/ApplyContextEdits/None) carries caller intent - NodeLifecycleCoordinator extracted from WorkflowExecutor - ProcessorPipeline supports mid-pipeline suspend/resume via executePostFrom() - ReviewPostProcessor refactored around ReviewOutcome sealed type - ExecutionPhaseSerializer/Deserializer in hensu-serialization - Server: InteractiveReviewHandler, ResumeRequest DTO, ExecutionEventNativeConfig, NativeImageConfig → CoreModelNativeConfig - CLI: review handlers updated for new ReviewOutcome return type - Docs: cross-referenced against source, fixed stale references and incorrect pipeline ordering Resolve: #71 Signed-off-by: Aleksandr Suvorov <asuvorov@hensu.io>
1 parent f68d4ad commit 0d7eb6f

118 files changed

Lines changed: 3464 additions & 1081 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,9 @@ fork/join, and sub-workflows.
189189
mid-execution, it resumes from the last checkpoint automatically.
190190
- **Tenant isolation.** Java 25 `ScopedValues` enforce strict context boundaries between concurrent
191191
workflows.
192-
- **Local daemon.** `hensu run` starts a warm resident process. Detach with `Ctrl+C`, re-attach
193-
with `hensu attach` — output is never lost.
192+
- **Local daemon.** `hensu daemon start` launches a warm background process that eliminates JVM
193+
cold-start. `hensu run` delegates to it automatically when running. Detach with `Ctrl+C`,
194+
re-attach with `hensu attach` — output is never lost.
194195
- **Native binary.** The server ships as a GraalVM native image. No JVM to manage, no classpath to
195196
debug.
196197

@@ -263,13 +264,14 @@ flowchart LR
263264

264265
### Modules
265266

266-
| Module | Role |
267-
|:---------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------|
268-
| **[hensu-core](hensu-core/README.md)** | Pure Java execution engine. State transitions, rubric evaluation, agent interactions. Zero external dependencies. |
269-
| **[hensu-dsl](hensu-dsl/README.md)** | Kotlin DSL that compiles `.kt` files into versioned Workflow Definitions (JSON). |
270-
| **[hensu-cli](hensu-cli/README.md)** | Local execution via `hensu run`. Server lifecycle: `build`, `push`, `pull`, `delete`, `list`. |
271-
| **[hensu-server](hensu-server/README.md)** | Multi-tenant GraalVM native server. SSE split-pipe for MCP tool routing. |
272-
| **[hensu-serialization](hensu-serialization/README.md)** | Jackson-based JSON serialization shared by the CLI and server. |
267+
| Module | Role |
268+
|:-----------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------|
269+
| **[hensu-core](hensu-core/README.md)** | Pure Java execution engine. State transitions, rubric evaluation, agent interactions. Zero external dependencies. |
270+
| **[hensu-dsl](hensu-dsl/README.md)** | Kotlin DSL that compiles `.kt` files into versioned Workflow Definitions (JSON). |
271+
| **[hensu-cli](hensu-cli/README.md)** | `run`, `validate`, `visualize`, `build`, `push`/`pull`/`delete`/`list`. Background daemon for warm starts. |
272+
| **[hensu-server](hensu-server/README.md)** | Multi-tenant GraalVM native server. SSE split-pipe for MCP tool routing. SSE execution event streaming. |
273+
| **[hensu-serialization](hensu-serialization/README.md)** | Jackson-based JSON serialization shared by the CLI and server. |
274+
| **[hensu-langchain4j-adapter](hensu-langchain4j-adapter)** | Bridges `hensu-core` Agent abstraction with LangChain4j `ChatModel` implementations. |
273275

274276
---
275277

docs/developer-guide-core.md

Lines changed: 111 additions & 17 deletions
Large diffs are not rendered by default.

docs/developer-guide-serialization.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ flowchart TD
3939
ws(["WorkflowSerializer\n(creates ObjectMapper)"])
4040
subgraph module["HensuJacksonModule"]
4141
direction LR
42-
poly(["addSerializer/Deserializer\n(Node, TransitionRule,\nAction, PlanStepAction)"]) ~~~ schema(["addDeserializer\n(WorkflowStateSchema\n– no mixin)"]) ~~~ mixins(["setMixInAnnotations\n(Workflow, AgentConfig,\nExecutionStep, …)"])
42+
poly(["addSerializer/Deserializer\n(Node, TransitionRule,\nAction, PlanStepAction,\nExecutionPhase)"]) ~~~ schema(["addDeserializer\n(WorkflowStateSchema\n– no mixin)"]) ~~~ mixins(["setMixInAnnotations\n(Workflow, AgentConfig,\nExecutionStep, …)"])
4343
end
4444
subgraph mixin["mixin/"]
4545
direction LR
@@ -49,7 +49,7 @@ flowchart TD
4949
end
5050
5151
core(["hensu-core\n(zero Jackson)"])
52-
server(["hensu-server\nNativeImageConfig\n(@RegisterForReflection)"])
52+
server(["hensu-server\nCoreModelNativeConfig\n(@RegisterForReflection)"])
5353
5454
ser -->|"depends on"| core
5555
ser -->|"reflection info"| server
@@ -101,7 +101,7 @@ At deserialization time Jackson:
101101
4. Calls each setter by name (via **reflection**)
102102
5. Calls `builder.build()` (via **reflection**)
103103

104-
Because `hensu-core` builders have `private` constructors, all three reflection calls require native-image registration. This is handled in `NativeImageConfig` in `hensu-server` — never in `hensu-core` itself.
104+
Because `hensu-core` builders have `private` constructors, all three reflection calls require native-image registration. This is handled in `CoreModelNativeConfig` in `hensu-server` — never in `hensu-core` itself.
105105

106106
**Registration in `HensuJacksonModule`:**
107107

@@ -124,6 +124,7 @@ Used in two distinct cases:
124124
| `TransitionRule` | `TransitionRuleSerializer` | `TransitionRuleDeserializer` |
125125
| `Action` | `ActionSerializer` | `ActionDeserializer` |
126126
| `PlanStepAction` | `PlanStepActionSerializer` | `PlanStepActionDeserializer` |
127+
| `ExecutionPhase` | `ExecutionPhaseSerializer` | `ExecutionPhaseDeserializer` |
127128

128129
**Case B — Native-image performance** where the mixin/builder pattern would require registering a private constructor and builder class for reflection, but the type is a single concrete class that can be deserialized more efficiently by direct field extraction:
129130

@@ -186,9 +187,9 @@ if (root.has("reviewConfig")) {
186187

187188
### When `treeToValue` is acceptable
188189

189-
If the target class contains a `java.time.Duration`, deeply nested types, or other fields where manual extraction would be error-prone and brittle, `treeToValue` is acceptable. The class must then be registered in `NativeImageConfig` in `hensu-server`.
190+
If the target class contains a `java.time.Duration`, deeply nested types, or other fields where manual extraction would be error-prone and brittle, `treeToValue` is acceptable. The class must then be registered in `CoreModelNativeConfig` in `hensu-server`.
190191

191-
Current exceptions (registered in `NativeImageConfig`):
192+
Current exceptions (registered in `CoreModelNativeConfig`):
192193

193194
| Class | Reason |
194195
|-------------------|---------------------------------------------------------|
@@ -222,7 +223,7 @@ Do **not** use `convertValue` to deserialize `hensu-core` domain objects — it
222223
- [ ] Are all fields primitives/strings/enums? → extract manually instead
223224
- [ ] Is the target an untyped `Map` or `List`? → use `convertValue` instead (no registration needed)
224225
- [ ] Does the class already have a custom deserializer? → use it via `mapper` (safe)
225-
- [ ] Added to `NativeImageConfig` if registering? → cross-check both places
226+
- [ ] Added to `CoreModelNativeConfig` if registering? → cross-check both places
226227

227228
---
228229

@@ -232,7 +233,7 @@ Java records have no inner `Builder` class — Jackson deserializes them via the
232233

233234
### Rule
234235

235-
> Any record type that is a field (direct or nested) of a mixin/builder-registered class must be registered in `NativeImageConfig`.
236+
> Any record type that is a field (direct or nested) of a mixin/builder-registered class must be registered in `CoreModelNativeConfig`.
236237
237238
### Current registrations
238239

@@ -273,7 +274,7 @@ Unlike the `treeToValue` pattern, these records do **not** need a custom deseria
273274
- [ ] Is it a `record` embedded in a mixin-registered builder type? → register it
274275
- [ ] Does it contain nested record fields? → register those too
275276
- [ ] Does it contain `Duration` or other complex JDK types? → `treeToValue` rule applies instead
276-
- [ ] Added to `NativeImageConfig` in `hensu-server`? → verify the entry is present
277+
- [ ] Added to `CoreModelNativeConfig` in `hensu-server`? → verify the entry is present
277278

278279
---
279280

@@ -356,7 +357,7 @@ context.setMixInAnnotations(MyConfig.class, MyConfigMixin.class);
356357
context.setMixInAnnotations(MyConfig.Builder.class, MyConfigBuilderMixin.class);
357358
```
358359

359-
3. **Register for reflection in `NativeImageConfig`** (in `hensu-server`):
360+
3. **Register for reflection in `CoreModelNativeConfig`** (in `hensu-server`):
360361

361362
```java
362363
@RegisterForReflection(
@@ -365,7 +366,7 @@ context.setMixInAnnotations(MyConfig.Builder.class, MyConfigBuilderMixin.class);
365366
MyConfig.class,
366367
MyConfig.Builder.class
367368
})
368-
public class NativeImageConfig {}
369+
public class CoreModelNativeConfig {}
369370
```
370371

371372
All three steps are required. Missing step 3 causes a silent runtime failure in native image: Jackson finds the builder class but cannot invoke the private constructor or `build()` method.
@@ -396,8 +397,8 @@ Any boolean field on a builder-pattern type must have a corresponding `@JsonProp
396397

397398
| What | Where to fix |
398399
|-----------------------------------------------|-------------------------------------------------------------|
399-
| Builder's private constructor not found | Add class to `NativeImageConfig` in server |
400-
| `build()` method not found at runtime | Add class to `NativeImageConfig` in server |
400+
| Builder's private constructor not found | Add class to `CoreModelNativeConfig` in server |
401+
| `build()` method not found at runtime | Add class to `CoreModelNativeConfig` in server |
401402
| `treeToValue` target fails with NPE/exception | Prefer manual extraction; if not feasible, register |
402403
| New serializable type breaks native build | Check the checklist in this guide, add mixin + registration |
403404

@@ -415,11 +416,12 @@ The root rule: **`hensu-core` owns no serialization metadata. `hensu-serializati
415416
| `TransitionRuleSerializer` / `TransitionRuleDeserializer` | Polymorphic `TransitionRule` (discriminator: `type`) |
416417
| `ActionSerializer` / `ActionDeserializer` | Polymorphic `Action` (discriminator: `type`) |
417418
| `PlanStepActionSerializer` / `PlanStepActionDeserializer` | Polymorphic `PlanStepAction` (discriminator: `type`) |
419+
| `ExecutionPhaseSerializer` / `ExecutionPhaseDeserializer` | Polymorphic `ExecutionPhase` (discriminator: `type`) |
418420
| `WorkflowStateSchemaDeserializer` | Direct-extraction deserializer for `WorkflowStateSchema`; avoids mixin reflection overhead in native image |
419421
| `plan/JacksonPlanResponseParser` | Parses LLM JSON responses into `PlannedStep` lists; strips markdown fences |
420422
| `mixin/*Mixin.java` | `@JsonDeserialize` bridge for builder-pattern types |
421423
| `mixin/*BuilderMixin.java` | `@JsonPOJOBuilder` configuration for builder inner classes |
422424

423425
> **See also**:
424426
> - [hensu-core Developer Guide — GraalVM](developer-guide-core.md#graalvm-native-image-constraints) for foundational native-image rules
425-
> - [hensu-server Developer Guide — NativeImageConfig](developer-guide-server.md#nativeimageconfig--jackson-reflection-registration) for registration patterns and the resource bundling rule
427+
> - [hensu-server Developer Guide — CoreModelNativeConfig](developer-guide-server.md#coremodelnativeconfig--jackson-reflection-registration) for registration patterns and the resource bundling rule

0 commit comments

Comments
 (0)