Failure Handling
Plans do not always succeed. An action can fail, a belief that a plan relies on may not be there, or a sub-goal may have no plan that can achieve it. ASTRA gives you two ways of dealing with failure:
try/recoverstatements, which handle a failure inside the plan where it happens, in the same way as exception handling in Java;goal failure events (
^!goal(...)), which let you write separate plans that respond when a goal could not be achieved.
What counts as a failure
A statement fails, and with it the plan that contains it, when:
a module action fails (the Java method returns
falseor throws an exception), includingS.fail()from theSystemmodule;the
fail;statement is executed;a
query(...)cannot be matched against the agent’s beliefs;a sub-goal (
!g) or test goal (?f) has no applicable plan;a sub-goal’s plan fails, and the failure is not handled further down.
try / recover
A try statement runs a block of statements. If any of them fails, the rest of the block is skipped and the recover block runs instead. Either way, the plan then continues with the statement after the try:
try {
C.println("trying");
S.fail();
C.println("not reached");
} recover {
C.println("recovered");
}
C.println("continues");
[main] trying
[main] recovered
[main] continues
The try block can contain sub-goals. If a sub-goal fails and the failure is not handled by the sub-goal itself (see below), the recover block of the calling plan runs:
try {
!nowhere(); // there is no plan for !nowhere()
} recover {
C.println("could not achieve !nowhere()");
}
If a statement in the recover block fails, that failure is handled in the normal way, as if the try statement itself had failed.
Goal failure events
When a plan that was adopted to achieve a goal fails, ASTRA generates a goal failure event for that goal. It is written with ^ in place of +:
plan ^!connect() {
C.println("could not connect");
}
A plan for ^!g(...) is selected like any other plan, so it can use variables and a context. When it finishes, the failure counts as handled: the plan that adopted the goal continues as if the goal had been achieved.
A common use is to retry a goal. In this example, connecting fails twice before it succeeds. Each failure is handled by re-adopting the goal:
agent Connector {
module Console C;
module System S;
types connection {
formula attempts(int);
}
initial attempts(0);
plan +!main(list args) {
!connect();
C.println("connected");
S.exit();
}
plan +!connect() : attempts(int N) & N < 2 {
-attempts(N);
+attempts(N + 1);
C.println("attempt " + (N + 1) + " fails");
S.fail();
}
plan +!connect() {
C.println("attempt succeeds");
}
plan ^!connect() {
!connect();
}
}
[main] attempt 1 fails
[main] attempt 2 fails
[main] attempt succeeds
[main] connected
Make sure that a retry cannot go on for ever: here, the attempts(...) belief limits the number of failing attempts.
Goal failure plans are also how the Practical Reasoning Programming Style writes its Recovery Plans.
Which handler is used?
When a statement fails, ASTRA works outwards from the plan where the failure happened until it finds something that can handle it:
a
trystatement around the failing statement, in the same plan;a
^!g(...)plan for the goal that the failing plan was trying to achieve;a
trystatement around the sub-goal in the plan that adopted that goal;a
^!plan for that plan’s goal, and so on, up to the top of the intention.
So a ^! plan for a goal takes priority over a try statement in the plan that adopted the goal. In the example below, !fetch() has its own failure plan, so the caller’s recover block is not used:
plan +!main(list args) {
try {
!fetch();
} recover {
C.println("not used: ^!fetch() handled the failure");
}
C.println("main continues");
}
plan +!fetch() { S.fail(); }
plan ^!fetch() { C.println("^!fetch() handled the failure"); }
[main] ^!fetch() handled the failure
[main] main continues
A failure deep inside a goal can be handled at any level above it. For example, if !outer() adopts !inner() and the plan for !inner() fails, a ^!outer() plan handles the failure when there is no ^!inner() plan.
Unhandled failures
If nothing handles a failure, the whole intention fails: ASTRA prints the failed statement and the goals that led to it, and drops the intention. Only that intention is affected. The agent’s other intentions, such as goals adopted with !!, carry on:
[main] Failed Action: S.fail()
[main] +!worker()
A ^! plan can itself fail. In that case the failure carries on outwards, as if the goal’s plan had failed without a failure plan.
Choosing between them
Use
try/recoverwhen the plan that is doing the work knows what to do if one particular step fails, such as using a default value or skipping an optional step.Use goal failure plans when the response belongs to the goal rather than to one plan: for example, retrying, trying a different approach, or tidying up after any of the goal’s plans fails.