6. Handling uncertainty and failure
An agent may lack a needed fact, an action may fail, or the environment may take time to reflect a change. Treat these as different situations. Choosing the right check makes both the program’s behavior and its failure paths easier to understand.
Choose a knowledge check
Construct |
When it succeeds |
When the formula does not hold |
|---|---|---|
|
Selects the true branch and binds matching variables |
Selects the alternative branch, if provided |
|
Continues with matching bindings |
Fails the plan |
|
Uses existing knowledge or a test-goal plan |
Fails if no applicable plan can supply the answer |
|
Continues when the formula holds |
Suspends until it holds or the timeout fails the wait |
Use a query for a prerequisite that must already be known. Use a test goal when the agent can actively find out. Use a wait when another process supplies the information, such as a sensor reporting the effect of a device action.
A test-goal plan must bind its requested outputs. Its completion is trusted: ASTRA does not automatically recheck that the requested belief now holds. End the plan with an explicit query when that confirmation matters. The detailed Test Goals page demonstrates fetching and caching missing knowledge.
Bound external waits
The light device plan uses wait(light(Target), 2000). If the action succeeds but the sensor never observes the result, the goal fails after two seconds. The timeout is part of this demonstration’s contract; choose a suitable value for the actual device.
A timeout does not undo an external operation. The light may change after the caller has stopped waiting. After a timeout, reconcile the observed state before retrying an operation whose effects cannot safely be repeated.
Recover at the right level
This complete program uses the module from Chapter 5 and handles an unsupported target locally:
agent ReliableController {
module example.LightDevice device;
module Console console;
module System system;
types lighting { formula light(string); }
plan +!main(list args) {
wait(light("off"), 2000);
try {
!light("dim");
} recover {
console.println("unsupported target rejected");
}
!light("on");
console.println("valid target achieved");
system.exit();
}
plan +!light(string Target) : light(Target) { }
plan +!light(string Target) {
device.set(Target);
wait(light(Target), 2000);
}
}
Run with -Dastra.main=ReliableController. The device rejects "dim", so the caller’s recovery block runs. It then requests a valid state and completes normally.
Use try/recover when the calling plan has a specific alternative for a failed step. A goal failure plan, plan ^!light(string Target), is appropriate when recovery belongs to that goal across all of its achievement plans.
A successfully completed failure plan counts as handling the goal’s failure, so its caller continues. Do not merely print an error in a failure plan if the caller must know the goal remains unachieved. Either establish a genuine fallback result, explicitly report the outcome through a protocol, or let failure propagate. A goal’s own failure handler takes precedence over a recovery block surrounding the caller’s subgoal.
Preserve evidence and progress
Belief updates and device operations are not transactions: changes already made are not automatically rolled back after failure. Record enough context to know what succeeded and what still needs repair. Bound retry counts, and retry only when there is a reason to expect progress.
Failure normally ends one intention, not all of the agent’s other work. That distinction becomes important when several requests or ongoing activities run concurrently.
Next: Managing ongoing activities.