Skip to content

Commit 05dd55f

Browse files
committed
feat(transition): add forgotten files
Resolve: #73 Signed-off-by: Aleksandr Suvorov <asuvorov@hensu.io>
1 parent 14382a2 commit 05dd55f

2 files changed

Lines changed: 106 additions & 0 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package io.hensu.core.execution.enricher;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import io.hensu.core.execution.EngineVariables;
6+
import io.hensu.core.execution.executor.ExecutionContext;
7+
import org.junit.jupiter.api.BeforeEach;
8+
import org.junit.jupiter.api.DisplayName;
9+
import org.junit.jupiter.api.Test;
10+
11+
@DisplayName("FeedbackContextInjector")
12+
class FeedbackContextInjectorTest extends EnricherTestBase {
13+
14+
private FeedbackContextInjector injector;
15+
16+
@BeforeEach
17+
void setUp() {
18+
injector = new FeedbackContextInjector();
19+
}
20+
21+
@Test
22+
@DisplayName("appends feedback section with heading when recommendation is present")
23+
void shouldAppendFeedbackWhenRecommendationPresent() {
24+
ExecutionContext ctx = ctx(null, null);
25+
ctx.getState().getContext().put(EngineVariables.RECOMMENDATION, "Needs more examples.");
26+
27+
String result = injector.inject("Write about AI.", minimalNode(), ctx);
28+
29+
assertThat(result)
30+
.startsWith("Write about AI.")
31+
.contains(FeedbackContextInjector.FEEDBACK_SECTION_PREFIX)
32+
.endsWith("Needs more examples.");
33+
}
34+
35+
@Test
36+
@DisplayName(
37+
"does not remove recommendation from context — cleanup is TransitionPostProcessor's job")
38+
void shouldNotRemoveRecommendationFromContext() {
39+
ExecutionContext ctx = ctx(null, null);
40+
ctx.getState().getContext().put(EngineVariables.RECOMMENDATION, "Fix structure.");
41+
42+
injector.inject("prompt", minimalNode(), ctx);
43+
44+
assertThat(ctx.getState().getContext()).containsKey(EngineVariables.RECOMMENDATION);
45+
}
46+
47+
@Test
48+
@DisplayName(
49+
"feedback injector is first in DEFAULT pipeline — agent sees feedback before format instructions")
50+
void shouldBeFirstInDefaultPipeline() {
51+
ExecutionContext ctx = ctx(null, null);
52+
ctx.getState().getContext().put(EngineVariables.RECOMMENDATION, "Fix structure.");
53+
54+
String result = EngineVariablePromptEnricher.DEFAULT.enrich("base", minimalNode(), ctx);
55+
56+
int feedbackPos = result.indexOf("### Previous Feedback");
57+
assertThat(feedbackPos).as("feedback section must be present").isGreaterThan(0);
58+
59+
assertThat(result.substring(0, feedbackPos).trim())
60+
.as("nothing between base prompt and feedback section")
61+
.isEqualTo("base");
62+
63+
// Recommendation still in context — cleanup is TransitionPostProcessor's job
64+
assertThat(ctx.getState().getContext()).containsKey(EngineVariables.RECOMMENDATION);
65+
}
66+
67+
@Test
68+
@DisplayName("no recommendation in context — prompt returned unchanged")
69+
void shouldReturnPromptUnchangedWhenNoRecommendation() {
70+
ExecutionContext ctx = ctx(null, null);
71+
72+
String result = injector.inject("Write about AI.", minimalNode(), ctx);
73+
74+
assertThat(result).isEqualTo("Write about AI.");
75+
}
76+
77+
@Test
78+
@DisplayName("blank recommendation — prompt returned unchanged")
79+
void shouldReturnPromptUnchangedWhenRecommendationBlank() {
80+
ExecutionContext ctx = ctx(null, null);
81+
ctx.getState().getContext().put(EngineVariables.RECOMMENDATION, " ");
82+
83+
String result = injector.inject("Write about AI.", minimalNode(), ctx);
84+
85+
assertThat(result).isEqualTo("Write about AI.");
86+
}
87+
88+
@Test
89+
@DisplayName("non-String recommendation — prompt returned unchanged")
90+
void shouldReturnPromptUnchangedWhenRecommendationNotString() {
91+
ExecutionContext ctx = ctx(null, null);
92+
ctx.getState().getContext().put(EngineVariables.RECOMMENDATION, 42);
93+
94+
String result = injector.inject("Write about AI.", minimalNode(), ctx);
95+
96+
assertThat(result).isEqualTo("Write about AI.");
97+
}
98+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Per-node retry budgets for bounded transitions (ticket #73).
2+
--
3+
-- Stores the namespaced retry counter map (keyed by "namespace:nodeId") so that
4+
-- bounded revise/retry budgets survive checkpoint, resume, and recovery. The
5+
-- '{}' default backfills pre-migration rows with an empty counter map, so no
6+
-- data migration is required.
7+
ALTER TABLE runtime.execution_states
8+
ADD COLUMN retry_counters JSONB NOT NULL DEFAULT '{}';

0 commit comments

Comments
 (0)