From d2e5ee660a45808b968d36799ec98e7714e35b31 Mon Sep 17 00:00:00 2001 From: Swetank Mohanty Date: Thu, 2 Jul 2026 22:10:01 +0400 Subject: [PATCH 1/3] coderbyte: Added implementation for goal-oriented action planner --- .../coderbyte/GoalOrientedActionPlanner.java | 146 ++++++++++++++++++ .../primekit/core/agents/Action.java | 17 ++ .../core/agents/IncidentWorldState.java | 27 ++++ .../primekit/core/agents/Plan.java | 13 ++ .../primekit/core/agents/Severity.java | 9 ++ .../GoalOrientedActionPlannerTest.java | 73 +++++++++ 6 files changed, 285 insertions(+) create mode 100644 src/main/java/com/shortthirdman/primekit/coderbyte/GoalOrientedActionPlanner.java create mode 100644 src/main/java/com/shortthirdman/primekit/core/agents/Action.java create mode 100644 src/main/java/com/shortthirdman/primekit/core/agents/IncidentWorldState.java create mode 100644 src/main/java/com/shortthirdman/primekit/core/agents/Plan.java create mode 100644 src/main/java/com/shortthirdman/primekit/core/agents/Severity.java create mode 100644 src/test/java/com/shortthirdman/primekit/coderbyte/GoalOrientedActionPlannerTest.java diff --git a/src/main/java/com/shortthirdman/primekit/coderbyte/GoalOrientedActionPlanner.java b/src/main/java/com/shortthirdman/primekit/coderbyte/GoalOrientedActionPlanner.java new file mode 100644 index 0000000..d81f671 --- /dev/null +++ b/src/main/java/com/shortthirdman/primekit/coderbyte/GoalOrientedActionPlanner.java @@ -0,0 +1,146 @@ +package com.shortthirdman.primekit.coderbyte; + +import com.shortthirdman.primekit.core.agents.Action; +import com.shortthirdman.primekit.core.agents.Plan; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.PriorityQueue; + +/** + * A planner that uses the A* search algorithm to find an optimal sequence of actions + * (a Plan) to transition from an initial state to a desired goal state. + * @author shortthirdman + * @since 1.2.0 + */ +public class GoalOrientedActionPlanner { + + /** + * Calculates the optimal plan to reach the goal state from the initial state + * using the provided set of available actions. + *

+ * This method implements the A* search algorithm, exploring states based on + * accumulated cost (g-score) and a heuristic estimating the remaining distance + * to the goal (f-score). + * + * @param initialState the starting state map containing key-value condition pairs + * @param goal the target state map containing required key-value condition pairs + * @param availableActions the list of actions the planner can utilize + * @return an {@link Optional} containing the optimal {@link Plan} if a valid sequence + * is found, or an empty {@link Optional} if the goal is unreachable + */ + public Optional plan( + Map initialState, + Map goal, + List availableActions) { + var open = new PriorityQueue<>(Comparator.comparingDouble(Node::f)); + var closed = new HashSet>(); + + open.add(new Node(initialState, List.of(), 0, heuristic(initialState, goal))); + + while (!open.isEmpty()) { + Node current = open.poll(); + + if (satisfies(current.state(), goal)) { + return Optional.of(new Plan(current.actions(), current.g())); + } + + if (!closed.add(current.state())) continue; + + for (Action action : availableActions) { + if (!canApply(action, current.state())) continue; + + Map next = apply(action, current.state()); + List chain = new ArrayList<>(current.actions()); + chain.add(action); + + double g = current.g() + action.cost(); + double f = g + heuristic(next, goal); + + open.add(new Node(next, chain, g, f)); + } + } + + return Optional.empty(); + } + + /** + * Evaluates whether an action can be performed in the given state by verifying + * that all preconditions of the action are met. + * + * @param action the action to evaluate + * @param state the current state to check against + * @return {@code true} if all preconditions are satisfied, {@code false} otherwise + */ + private boolean canApply(Action action, Map state) { + return action.preconditions().entrySet().stream() + .allMatch(e -> Objects.equals(state.get(e.getKey()), e.getValue())); + } + + /** + * Applies the effects of an action to a given state, generating a new resulting state. + *

+ * This method does not mutate the original state; instead, it returns a newly allocated + * map containing the merged state and effects. + * + * @param action the action whose effects are to be applied + * @param state the initial state before the action is executed + * @return a new state map reflecting the applied effects + */ + private Map apply(Action action, Map state) { + Map result = new HashMap<>(state); + result.putAll(action.effects()); + return result; + } + + /** + * Checks if the given state satisfies all conditions defined in the goal. + * + * @param state the current state to evaluate + * @param goal the target conditions that must be met + * @return {@code true} if the state contains all goal conditions with matching values, + * {@code false} otherwise + */ + private boolean satisfies(Map state, Map goal) { + return goal.entrySet().stream() + .allMatch(e -> Objects.equals(state.get(e.getKey()), e.getValue())); + } + + /** + * Calculates a heuristic value estimating the cost to reach the goal from the current state. + *

+ * The current implementation uses the number of unsatisfied goal conditions as the heuristic. + * This acts as an admissible heuristic assuming each action costs at least 1.0 and satisfies + * at most one goal condition. + * + * @param state the current state + * @param goal the target state + * @return the count of goal conditions that differ from the current state + */ + private double heuristic(Map state, Map goal) { + return goal.entrySet().stream() + .filter(e -> !Objects.equals(state.get(e.getKey()), e.getValue())) + .count(); + } + + /** + * Represents a discrete node in the A* search tree. + * + * @param state the current world state at this node + * @param actions the sequence of actions taken to reach this state + * @param g the exact cumulative cost from the initial state to this node (g-score) + * @param f the estimated total cost from the initial state to the goal through this node (f-score = g + heuristic) + */ + private record Node( + Map state, + List actions, + double g, + double f + ) {} +} diff --git a/src/main/java/com/shortthirdman/primekit/core/agents/Action.java b/src/main/java/com/shortthirdman/primekit/core/agents/Action.java new file mode 100644 index 0000000..cd5498a --- /dev/null +++ b/src/main/java/com/shortthirdman/primekit/core/agents/Action.java @@ -0,0 +1,17 @@ +package com.shortthirdman.primekit.core.agents; + +import java.util.Map; + +/** + * Represents an action that can be executed to transition between states. + * + * @param name the unique identifier or descriptive name of the action + * @param preconditions the state requirements that must be met before this action can be executed + * @param effects the state changes resulting from the execution of this action + * @param cost the numerical cost associated with performing this action + */ +public record Action(String name, + Map preconditions, + Map effects, + double cost) { +} diff --git a/src/main/java/com/shortthirdman/primekit/core/agents/IncidentWorldState.java b/src/main/java/com/shortthirdman/primekit/core/agents/IncidentWorldState.java new file mode 100644 index 0000000..0ddf9ac --- /dev/null +++ b/src/main/java/com/shortthirdman/primekit/core/agents/IncidentWorldState.java @@ -0,0 +1,27 @@ +package com.shortthirdman.primekit.core.agents; + +/** + * @param serviceDegraded whether service is degraded + * @param recentDeployment whether deployment is recent + * @param serviceHealthy whether service is healthy + * @param oncallPaged whether service is on-call paged + * @param restartAttempted whether service has been attempted restart + * @param severity the level of {@link Severity} of the incident + */ +public record IncidentWorldState(boolean serviceDegraded, + boolean recentDeployment, + boolean serviceHealthy, + boolean oncallPaged, + boolean restartAttempted, + Severity severity) { + + /** + * Initialize incident with whether service is healthy + * @param healthy flag to set if service is healthy or not + * @return the initialized {@link IncidentWorldState} + */ + public IncidentWorldState withServiceHealthy(boolean healthy) { + return new IncidentWorldState(serviceDegraded, recentDeployment, healthy, + oncallPaged, restartAttempted, severity); + } +} diff --git a/src/main/java/com/shortthirdman/primekit/core/agents/Plan.java b/src/main/java/com/shortthirdman/primekit/core/agents/Plan.java new file mode 100644 index 0000000..0733eec --- /dev/null +++ b/src/main/java/com/shortthirdman/primekit/core/agents/Plan.java @@ -0,0 +1,13 @@ +package com.shortthirdman.primekit.core.agents; + +import java.util.List; + +/** + * Represents a calculated sequence of actions to achieve a specific goal, + * along with the total accumulated cost. + * + * @param actions the ordered list of actions to be executed + * @param totalCost the sum of the costs of all actions in the plan + */ +public record Plan(List actions, double totalCost) { +} diff --git a/src/main/java/com/shortthirdman/primekit/core/agents/Severity.java b/src/main/java/com/shortthirdman/primekit/core/agents/Severity.java new file mode 100644 index 0000000..30f0f33 --- /dev/null +++ b/src/main/java/com/shortthirdman/primekit/core/agents/Severity.java @@ -0,0 +1,9 @@ +package com.shortthirdman.primekit.core.agents; + +public enum Severity { + + HIGH, + MEDIUM, + LOW, + NONE +} diff --git a/src/test/java/com/shortthirdman/primekit/coderbyte/GoalOrientedActionPlannerTest.java b/src/test/java/com/shortthirdman/primekit/coderbyte/GoalOrientedActionPlannerTest.java new file mode 100644 index 0000000..e716d64 --- /dev/null +++ b/src/test/java/com/shortthirdman/primekit/coderbyte/GoalOrientedActionPlannerTest.java @@ -0,0 +1,73 @@ +package com.shortthirdman.primekit.coderbyte; + +import com.shortthirdman.primekit.core.agents.Action; +import com.shortthirdman.primekit.core.agents.Plan; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +class GoalOrientedActionPlannerTest { + + private static GoalOrientedActionPlanner planner; + private static List actions; + private static Map goal; + private static Map initialState; + + @BeforeAll + static void setUp() { + planner = new GoalOrientedActionPlanner(); + initialState = Map.of( + "service.degraded", true, + "deployment.recent", true, + "service.healthy", false, + "oncall.paged", false + ); + + goal = Map.of("service.healthy", true); + + actions = List.of( + new Action("restartService", + Map.of("service.degraded", true), + Map.of("service.degraded", false, "service.healthy", true), + 1.0), + + new Action("rollbackDeployment", + Map.of("service.degraded", true, "deployment.recent", true), + Map.of("service.degraded", false, "service.healthy", true, + "deployment.recent", false), + 5.0), + + new Action("pageOncall", + Map.of("service.degraded", true), + Map.of("oncall.paged", true), + 10.0) + ); + } + + @AfterAll + static void tearDown() { + planner = null; + goal = Map.of(); + initialState = Map.of(); + actions = List.of(); + } + + @Test + void plan() { + Plan actualPlan = planner.plan(initialState, goal, actions).orElseThrow(); + + var expectedAction = new Action("restartService", + Map.of("service.degraded", true), + Map.of("service.degraded", false, "service.healthy", true), + 1.0); + Plan expectedPlan = new Plan(List.of(expectedAction), 1.0); + + Assertions.assertThat(actualPlan).isNotNull(); + Assertions.assertThat(actualPlan).isEqualTo(expectedPlan); + Assertions.assertThat(actualPlan).hasNoNullFieldsOrProperties(); + } +} \ No newline at end of file From 781ad1961ef2ee96d8daffb409d3e9a8e7fab42a Mon Sep 17 00:00:00 2001 From: Swetank Mohanty Date: Thu, 2 Jul 2026 22:11:04 +0400 Subject: [PATCH 2/3] docs: Added badges for integration with LLM --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 83cd1d9..6c97732 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # PrimeKit Challenges +[![Chat with Repo](https://badge.forgithub.com/shortthirdman-org/PrimeKit-Challenges?badge=chat)](https://uithub.com/shortthirdman-org/PrimeKit-Challenges) +[![LLM Context](https://badge.forgithub.com/shortthirdman-org/PrimeKit-Challenges?badge=context)](https://uithub.com/shortthirdman-org/PrimeKit-Challenges) + PrimeKit Coding Challenges > A collection of Java-based solutions designed to tackle typical coding problems asked in software development interviews. From 67756c7145ff557230b5341e8416a5be61d0fc4b Mon Sep 17 00:00:00 2001 From: Swetank Mohanty Date: Thu, 2 Jul 2026 22:12:34 +0400 Subject: [PATCH 3/3] pom: Upgraded minimum version for Maven and maximum JVM version upto 25 --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f55ff53..22234b6 100644 --- a/pom.xml +++ b/pom.xml @@ -206,10 +206,10 @@ - [3.8.6,4.0.0) + [3.8.8,4.0.0) - [15,23) + [15,26) @@ -274,6 +274,7 @@ 3.5.0 10 + true true plain true