Add a new typed action¶
You want to insert a new step into the pipeline and have GOAP route through it automatically — no hand-wired call order. The planner derives the sequence from what each action consumes and produces, so you add a typed producer and re-point whatever should consume it.
This is the Lab 2 shape: inserting a research step between shortlist and assemble.
Lab 2 vs the final structure
The snippets below show the end-state structure (main), where each agent method is a thin
wrapper delegating to a shared ConfPlanningCapabilities @Service (steps.research(...)). That
service is introduced in Lab 5. In Lab 2 there is no steps field — the prompt body and
id resolution live inline in ConfPlannerAgent.researchSessions. The GOAP lesson is identical
either way; only where the body lives differs.
1. Add the domain records the action moves¶
Domain types are immutable Java records under domain/. For a research step:
public record SessionInsight(Session session, String whyRelevant, double matchScore) {}
public record ResearchedSessions(List<SessionInsight> insights) {}
Keep the id-only idiom: the model emits ids, plain code resolves ids back to Session from the
catalog.
2. Declare the @Action on the agent¶
Add the method to the agent class (e.g. ConfPlannerAgent). Its parameter types are what it
consumes; its return type is what it produces:
@Action
ResearchedSessions researchSessions(CandidateSessions candidates, Ai ai) {
return steps.research(candidates, ai);
}
Put the prompt body and id resolution in the shared ConfPlanningCapabilities @Service if more
than one agent will reuse it; keep the agent method a thin wrapper.
3. Re-point the consumer to the new type¶
Change the downstream action to consume the new type, so the planner must run the new step first:
// was: DraftSchedule assembleSchedule(AttendeeProfile profile, CandidateSessions candidates, Ai ai)
DraftSchedule assembleSchedule(AttendeeProfile profile, ResearchedSessions researched, Ai ai) { ... }
Do not call researchSessions directly from assembleSchedule; let the types drive the order.
(See goal-oriented planning for why.)
4. Build and read the plan¶
The planning log should now read … → shortlist → research → assemble, with the new action between
the others purely because research produces what assemble now consumes.
If you want the action to use a web tool¶
@Action has no toolGroups member. Add the tool group on the prompt runner:
CoreToolGroups.WEB is the string "web". The tool group must be provided by an MCP tool server
at runtime; the default no-Docker lab path does not wire one.
If the producer can fail and you want a clean replan¶
If the action may return null for a missing dependency, the planner marks the produces/consumes
link unsatisfied and replans rather than crashing. To make a step re-runnable as part of an
invariant loop, set canRerun = true — see Add a guardrail.
For the @Action / @AchievesGoal / @Condition parameters, see the
annotations reference. For why goal-oriented planning beats a
hand-coded sequence, see About GOAP planning.