Skip to content

Define runtime failure and action outcome contracts #115

Open

Description

@jaagut

Current state

DSD.update() catches ordinary execution exceptions, logs them, and returns no status. There is no configured retry, fallback, or fail-fast policy. Actions complete by calling pop(); completion does not distinguish success from failure.

The pending changes in #122 preserve log-and-continue and add last_error, cleared at the beginning of each update. That field contains the first error recorded during the update, which may come from execution, cleanup, or debug publishing. #122 also replaces process termination on an empty decision outcome with a normal library error. These changes improve observability but do not define recovery or action outcomes.

Problem and relevance

A host cannot reliably decide whether to continue, restart, or select a fallback from update()'s return value. Reading last_error helps, but an execution failure and a debug publisher failure can require different responses. If an action fails before calling pop(), it can remain active and run again on the next update without an explicit retry policy.

Likewise, a completed action always advances the same chain. A behavior cannot express “continue on success, choose recovery on failure” through action completion itself. Applications must currently encode this through blackboard state and decisions. This matters for behavior supervisors, error reporting, and actions with side effects.

Options

  1. Keep compatibility behavior and add an explicit update result/error policy. Return a result identifying execution, cleanup, and debug errors; retain last_error for convenience. Default to logging, with an opt-in mode that propagates execution errors after required cleanup/debug handling.
    • Benefits: incremental adoption; supervisors can distinguish error sources; existing actions and behavior files continue working.
    • Caveats: retaining the stack is not a recovery strategy; callers must decide when another update is appropriate. Multiple errors need a defined ordering and representation.
  2. Propagate execution exceptions by default. Let the host choose whether to retry, interrupt, or stop scheduling updates. Report debug errors separately and preserve the original exception if cleanup also fails.
    • Benefits: failures cannot be silently overlooked by the caller; uses familiar Python exception handling.
    • Caveats: breaks callers expecting update() to continue without raising; does not itself define action success/failure or undo side effects.
  3. Introduce explicit action outcomes and failure branches. Define running, succeeded, and failed, plus how chains and recovery branches consume them. Keep programming exceptions distinct from expected action failure.
    • Benefits: behavior-level recovery becomes expressible and testable; distinguishes expected failure from implementation bugs.
    • Caveats: requires an API/DSL design and migration of actions; cancellation, cleanup, and retry semantics must also be specified.

Recommendation

Start with option 1: preserve the default, expose structured error sources, and offer opt-in propagation of execution failures. Specify that cleanup continues after an error and secondary cleanup/debug errors do not replace the primary execution error. Test callers that retry, interrupt, and stop after a failed update.

Treat option 3 as a separate follow-up based on concrete consumer examples; it should not be introduced merely as another way to report Python exceptions. Audit existing callers before considering option 2 as the default.

I am working on behalf of @jaagut.

Activity

  1. Flova commented on Oct 14, 2026

    @Flova
    Member

    I think the best options would be to give the host the exception (I think the behavior catches them either way in its tick) or do the logging and continuation we do now. I don't think we currently have a use case where we have recoverable exceptions that need some other host code path than a retry, so I would not over engineer this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions