Package com.tnsai.planning


package com.tnsai.planning
Planning system for goal-oriented agents.

This package provides STRIPS-style planning with backward chaining:

Quick Start


 // 1. Define a role with goals
 @RoleSpec(
     name = "Soldier",
     goals = {
         @Goal(name = "survive", condition = "health > 0", priority = Priority.CRITICAL),
         @Goal(name = "armed", condition = "ammo > 10")
     }
 )
 public class SoldierRole extends Role {
     @State private int health = 30;
     @State private int ammo = 5;

     @ActionSpec(
         precondition = "health <= 50",
         postcondition = "health = 100",
         fulfills = {"survive"}
     )
     public void heal() { this.health = 100; }

     @ActionSpec(
         precondition = "ammo <= 10",
         postcondition = "ammo = 50",
         fulfills = {"armed"}
     )
     public void reload() { this.ammo = 50; }
 }

 // 2. Execute planning
 SoldierRole role = new SoldierRole();
 PlanExecutor executor = new PlanExecutor(role);
 List<String> executed = executor.think();
 // Result: ["heal", "reload"]
 

Backward Chaining

When an action's precondition isn't satisfied, the planner recursively finds actions that can enable it:

 Goal: enterRoom (insideRoom == true)

 enterRoom action:
   precondition: doorUnlocked == true  // NOT satisfied
   postcondition: insideRoom = true

 unlockDoor action:
   precondition: hasKey == true        // NOT satisfied
   postcondition: doorUnlocked = true

 findKey action:
   precondition: hasKey == false       // SATISFIED
   postcondition: hasKey = true

 Resulting plan: [findKey, unlockDoor, enterRoom]
 

Executing a plan

Two surfaces execute a plan: PlanExecutor.think() and core's PlannerHandle SPI, which this module implements with an adapter over the same Planner. They share one invariant:

  • A step that declares a precondition is measured against the real @State as the preceding body left it — never against the planner's simulation of the preceding steps.
  • A step whose precondition has stopped holding is never executed.

Everything else about the two differs, because their APIs have different shapes:

  • Reporting. think() returns a list of executed steps, so a step it refuses is simply absent from it; the adapter returns one row per step, so it records a failed step result and reports no success.
  • What follows a refusal. think() abandons the rest of the plan; the adapter carries on with the remaining steps.
  • Skipping. think() skips a stale step when the planner declares goals and none is left unsatisfied — the plan's purpose was met another way — and otherwise raises PlanningFailureException.Reason.PRECONDITION_STALE. The adapter has no such distinction and always records a failure.
  • When state is measured. think() extracts beliefs afresh before each step with a precondition; the adapter extracts once and refreshes after every body that ran. The two coincide unless something outside the plan writes @State in between.
  • Goals. After the last step think() raises PlanningFailureException.Reason.GOAL_UNSATISFIED_AFTER_EXECUTION for a goal the plan set out to establish and the bodies did not. The adapter checks no goals; the final state it returns is what a caller checks them against.
Author:
TnsAI Team
See Also:
  • Goal
  • State
  • ActionSpec