Healing API¶
healing ¶
Healing primitives — watchdogs, recovery actions, model fallback.
The cognitive substrate's "immune system." Watchdogs subscribe to the
:class:EventBus, detect stall / loop / regression patterns, and feed
:class:RecoveryAction directives through a :class:WatchdogHook into
the existing :class:HookDecision pipeline.
See :doc:/concepts/healing for the full picture; this module hosts:
- :class:
HealingConfig— frozen dataclass. - :class:
WatchdogProtocol + :class:DetectionPydantic model. - :class:
StallDetector/ :class:LoopDetector/ :class:RegressionDetector— concrete watchdogs. - :class:
RecoveryActiondiscriminated union + :class:WatchdogHook. - :func:
resolve_model_with_fallback+ :class:FallbackModel. - :class:
HealingRunner— wires everything to a :class:Workbench's bus.
HealingConfig
dataclass
¶
HealingConfig(
stall_timeout_s: float = 60.0,
loop_threshold_k: int = 3,
loop_window_n: int = 10,
regression_window_n: int = 5,
regression_drop_threshold: float = 0.2,
poll_interval_s: float = 1.0,
fallback_models: tuple[str, ...] = tuple(),
enable_stall: bool = True,
enable_loop: bool = True,
enable_regression: bool = False,
)
Knobs for healing primitives. Pass explicitly to :class:HealingRunner.
stall_timeout_s
class-attribute
instance-attribute
¶
Detector fires when an open tool call exceeds this duration.
loop_threshold_k
class-attribute
instance-attribute
¶
Same (tool_name, args_hash) more than k times in the
sliding window triggers :class:LoopDetector.
loop_window_n
class-attribute
instance-attribute
¶
Sliding-window size for :class:LoopDetector — number of recent
tool.before events tracked.
regression_window_n
class-attribute
instance-attribute
¶
Number of recent metacognition.confidence values
:class:RegressionDetector averages over each half (head vs tail).
regression_drop_threshold
class-attribute
instance-attribute
¶
Confidence drop (head_mean − tail_mean) required to trigger
:class:RegressionDetector.
poll_interval_s
class-attribute
instance-attribute
¶
How often :class:HealingRunner polls watchdogs.
fallback_models
class-attribute
instance-attribute
¶
Ordered list of provider:model_id strings for
:func:resolve_model_with_fallback. Empty disables auto-fallback.
enable_regression
class-attribute
instance-attribute
¶
Off by default — needs metacognition.confidence events
flowing on the bus.
FallbackModel ¶
FallbackModel(
models: list[Model],
*,
bus: EventBus | None = None,
transient_predicate: Callable[[BaseException], bool]
| None = None,
)
pydantic-AI :class:Model wrapping a chain of fallback models.
On transient failure during :meth:request or
:meth:request_stream, advances _idx to the next model. The
advance is sticky across requests in the same instance — once the
primary failed, subsequent calls go straight to the fallback.
Non-transient failures propagate.
Source code in orqest/healing/fallback.py
request_stream
async
¶
request_stream(
messages: list[ModelMessage],
model_settings: ModelSettings | None,
model_request_parameters: ModelRequestParameters,
run_context: Any = None,
) -> AsyncIterator[Any]
Streaming variant. Mid-stream errors propagate (we may have already yielded chunks). Only pre-stream failures fall back.
Source code in orqest/healing/fallback.py
LoopDetector ¶
Detects an agent looping on the same tool call.
Maintains a deque of the last window_n (tool_name, args_hash)
pairs. When the current pair appears more than threshold_k times
in the window, raises a Detection. Each fire suppresses subsequent
detections until a different pair appears (so the watchdog doesn't
spam the same loop).
Source code in orqest/healing/loop.py
AbortRun ¶
Abort the compound flow.
EscalateToUser ¶
Stop autonomous execution and ask the user a question.
WatchdogHook ¶
WatchdogHook(
watchdogs,
*,
policy: Callable[[Detection], RecoveryAction]
| None = None,
bus: EventBus | None = None,
)
ToolHook that consults registered watchdogs and converts pending
Detections into :class:HookDecision directives.
Runs the policy on the first watchdog whose :meth:signal
returns a Detection in registration order. Subsequent watchdogs are
still polled so they advance their internal state, but their
detections are dropped (no second decision fires per call).
Returns None from :meth:after_tool and :meth:on_error —
decisions only fire on :meth:before_tool (the natural seam where
we can replace/skip the next call).
Source code in orqest/healing/recovery.py
RegressionDetector ¶
Confidence-trend watchdog.
Maintains a sliding window of the last window_n confidence
values. Fires when the head-half mean exceeds the tail-half mean by
at least drop_threshold — i.e. the agent is becoming less
certain over time.
Cumulative-mean diff over half-windows is robust against single- sample noise without needing full statistics. Window size of 4-6 typically suffices.
Source code in orqest/healing/regression.py
HealingRunner ¶
HealingRunner(
config: HealingConfig,
*,
bus: EventBus,
api_key: ApiKeyArg | None = None,
watchdogs: list[Watchdog] | None = None,
)
Owns the healing lifecycle for a single :class:EventBus.
Constructs watchdogs from :class:HealingConfig, subscribes them
to the bus, and runs a poll loop that:
- calls each watchdog's :meth:
signalperiodically (so detectors that compute on demand, like :class:StallDetector, fire); - emits
healing.detectionevents for each Detection produced.
The :class:WatchdogHook is exposed as runner.hook for the
consumer to register on a :class:HookRunner.
The fallback :class:Model (if configured) is exposed as
runner.model; pass it to :class:BaseAgent.
Source code in orqest/healing/runner.py
StallDetector ¶
Tracks open tool calls; raises :class:Detection when one exceeds
timeout_s.
Subscribes to tool.before to mark a call as open and
tool.after / tool.error to close it. :meth:signal is
polled by :class:HealingRunner and returns at most one Detection
per open call (subsequent polls for the same open call are
suppressed).
Source code in orqest/healing/stall.py
Detection ¶
A signal raised by a :class:Watchdog when its condition fires.
Watchdog ¶
A subsystem that observes the EventBus and raises Detections.
Implementations subscribe via :meth:subscribe and either:
- raise inline from event handlers, caching the latest Detection
for the next :meth:
signalpoll (LoopDetector,RegressionDetector); or - compute on demand at poll time (
StallDetector).
Subscriptions must be idempotent — calling :meth:subscribe twice
must not cause duplicate handler dispatch.
resolve_model_with_fallback ¶
resolve_model_with_fallback(
models: list[str],
*,
api_key: ApiKeyArg,
bus: EventBus | None = None,
transient_predicate: Callable[[BaseException], bool]
| None = None,
) -> Model
Resolve a chain of provider:model_id strings into a single
:class:FallbackModel.
Resolution failures (unknown provider, missing SDK, missing per-
provider key) are logged at DEBUG and skipped. The chain remains
valid as long as one entry resolves; raises ValueError if
none do.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models
|
list[str]
|
Ordered list — first is primary. |
required |
api_key
|
ApiKeyArg
|
Single key (used for every provider) or per-provider map. |
required |
bus
|
EventBus | None
|
Optional :class: |
None
|
transient_predicate
|
Callable[[BaseException], bool] | None
|
Override the default classifier. |
None
|
Source code in orqest/healing/fallback.py
default_policy ¶
Map a Detection to a sensible default RecoveryAction.
Consumers can override by passing a custom policy to
:class:WatchdogHook. The default is conservative: stalls/loops
abort by default; regressions also abort.