Error Handling

Turn uses monadic error routing instead of exception-based error handling. This is a deliberate design choice: in agentic software, stochastic failures from LLM inference are expected outcomes, not exceptional events. The error model is built to make failure handling explicit, composable, and impossible to accidentally ignore.


Philosophy

IMPORTANT

Turn does not have try/catch. Hallucinations, malformed LLM responses, and tool failures are anticipated results — they are Result::Err values, not stack-unwinding exceptions. Hiding stochastic failures behind exception handlers obfuscates control flow and leads to agents that silently corrupt their state.

The core principle is that every operation that can fail must return a value that the developer explicitly matches against. There is no implicit propagation, no unchecked exception, and no silent swallowing of errors. If a developer does not handle a Result::Err, the VM propagates it as a process exit signal — which is visible, debuggable, and structurally handled by supervisors.


Result and Option

Result\<T, E\>

The primary error type, returned by infer and any fallible tool call. A Result is either Ok(value) on success or Err(error) on failure. The developer must pattern-match against both branches to extract the contained value. If a Result::Err is used without being matched (for example, passed to an arithmetic operation or appended to context), the VM treats it as an unhandled error and converts it into a process exit signal.

Option\<T\>

Returned by recall(key) when the requested key may not exist in the process's memory store. An Option evaluates to Some(value) when the key is present and None when it is absent. Like Result, the developer must handle both branches. This eliminates the class of bugs where null values propagate silently through the program and surface as confusing errors far from the actual cause.


Process Exit Signals

When a process fails due to an unhandled error, token budget exhaustion, or a capability violation, the VM does not crash the entire program. Instead, it follows Erlang's "let it crash" philosophy:

Exit Signal Propagation

When a process terminates abnormally, the VM generates a ProcessExit signal containing the exit reason. Every process linked to the failed process receives this signal in its mailbox. Supervisors can match on exit reasons and decide whether to restart the child, escalate the failure, or take corrective action.

The sequence of events is:

  1. The process encounters an unrecoverable error. This could be an unhandled Result::Err, exhaustion of the token budget, an attempt to use a revoked capability, or a panic in a native tool handler.
  2. The VM generates a ProcessExit signal. The signal includes the process's Pid, the exit reason (a structured value), and the process's final state at the time of failure.
  3. All linked processes receive the signal. Processes that called link pid receive the exit signal in their mailbox as a special message. They can match on the exit reason and respond accordingly.
  4. Supervisors restart or escalate. A supervisor process is simply a Turn process that spawns children, links to them, and loops on receive to handle exit signals. There is no special supervisor syntax — the pattern emerges from the language primitives.

This design isolates failures to individual processes. A single crashed agent does not bring down the entire system. The supervision tree provides structural fault tolerance: errors are contained, reported, and handled at the appropriate level of the process hierarchy.