3. Writing plans
A plan states which event it handles, when it is applicable and what it does. The event supplies the request or observation; the context selects a suitable response; the body performs that response.
plan +!light(string Target) : light(Target) { }
This plan handles a light goal only when its target is already observed. Its empty body is intentional: no action is needed. Place it before the plan that changes the light. ASTRA chooses the first applicable matching plan; it does not automatically try every alternative after a selected plan fails.
Choose between plans and conditions
Use separate plans when alternatives express different ways to achieve the same goal. Use if when a small local decision is part of one strategy. The distinction helps the reader see the agent’s choices without tracing a long procedural body.
An arithmetic variable is declared with a type and can be assigned later:
int Remaining = 3;
while (Remaining > 0) {
!light("on");
!light("off");
Remaining = Remaining - 1;
}
This repeats a finite sequence. A recursive subgoal can express the same decomposition, with a base-case plan to stop recursion. Neither form should repeatedly check a condition that will become true only through another event: wait(...) expresses that dependency and suspends the intention.
Work with lists
A list can contain values, function terms or nested lists. Typed iteration requires elements matching the loop variable:
list States = ["on", "off", "on"];
forall (string State : States) {
!light(State);
}
forall iterates list elements. foreach iterates solutions to a formula query; these serve different purposes. For example, foreach (light(string Device, string State)) visits the known device/state matches in a model with several lights.
List patterns such as [string Head | list Tail] support recursive processing. The Prelude module provides indexed readers, size, sorting and other operations. Its modifying actions change the supplied list; its typed readers require the stored type to match.
Distinguish goals from actions
!light("on") asks the agent to choose a plan. device.set("on") invokes a particular module action. The first gives the program a place to select alternatives, repair prerequisites or recognize that the target is already satisfied. The second performs one concrete step.
Use !goal(...) when the next statement needs its result. Use !!goal(...) to start independent work, then coordinate its completion explicitly if needed. Sending a message is also asynchronous: it does not wait for the receiver to complete a task.
Make execution understandable
Choose names that reveal the desired result. Keep contexts readable and place specific cases before broad defaults. Do not assume an unguarded empty plan proves a goal’s state: it simply permits the plan to complete.
For demonstrations, print milestones such as an observed final state. A program with several intentions may print intermediate messages in different orders. Correctness should depend on beliefs and explicit coordination, not on a particular console sequence.