Errors & validation
Return Err(ActionError) and the agent receives a structured error with a fixed code, not a prose string it has to parse.
| Constructor | Meaning |
|---|---|
ActionError::field(name, msg) | One field is invalid |
ActionError::validation([(f, e), …]) | Several fields at once — preferred for forms |
ActionError::action_not_available() | Not callable in the current state |
ActionError::blocked_by_modal() | A modal owns input right now |
ActionError::window_not_found() | Target window is gone |
ActionError::action_timeout() | The operation took too long |
ActionError::stale_state(version) | The caller's expectedVersion is out of date |
ActionError::custom(code, msg) | Anything else in the fixed code set |
Report every problem at once
rust
// Shared by the on-screen notice and the error path, so they never disagree.
fn problems(&self) -> Vec<(&'static str, &'static str)> {
let mut out = Vec::new();
if self.recipient.is_empty() { out.push(("recipient", "required")); }
if self.amount <= 0 { out.push(("amount", "must be positive")); }
out
}
// In the handler:
let problems = this.problems();
if !problems.is_empty() {
return Err(ActionError::validation(problems));
}Tip
Derive both the on-screen notice block and the error from the same problems() method. The human and the agent then never see different verdicts.
You rarely construct stale_state
Purview checks expectedVersion itself, before your handler runs — see Free protocol behaviour. The constructor exists for the rare case where your own business logic detects a conflict the projection could not.