You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
└── DispatchSkippedError # Raised from on_before_dispatch to short-circuit (no retry)
109
112
```
110
113
114
+
`ErrorCategory` (StrEnum: `TRANSIENT`, `PERMANENT`, `RESOURCE_EXHAUSTION`, `UNKNOWN`) is provided for callers that want to categorize exceptions inside `on_after_dispatch` (e.g., a circuit breaker that only counts transient failures toward its open threshold). The library doesn't auto-categorize.
Copy file name to clipboardExpand all lines: docs/changelog.md
+24Lines changed: 24 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,30 @@ The Changelog documents all notable changes made to FastroAI. This includes new
6
6
7
7
---
8
8
9
+
## [0.6.0] - May 7, 2026
10
+
11
+
#### Added
12
+
-**Dispatch hooks on `FastroAgent`** by [@igorbenav](https://github.com/igorbenav)
13
+
-`on_before_dispatch` — async callback fired before each `agent.run()` attempt inside the retry loop. Raising `DispatchSkippedError` (or a subclass) short-circuits the dispatch — the retry loop propagates the error without retrying, and `on_after_dispatch` is not called.
14
+
-`on_after_dispatch` — async callback fired after each attempt with the exception that occurred, or `None` on success. Use for circuit-breaker recording, retry-budget tracking, alerting.
15
+
- Hooks fire **per attempt** (every retry triggers them again), letting downstream guards see each individual try rather than only the final outcome.
16
+
- Pre-flight rejections like `CostBudgetExceededError` skip both hooks — they raise before the dispatch loop is entered.
17
+
18
+
-**`DispatchSkippedError`** exception — subclass of `FastroAIError`. Raise from `on_before_dispatch` to fail fast on circuit-breaker-open / kill-switch / rate-limit signals without burning a retry slot.
19
+
20
+
-**`ErrorCategory`** StrEnum (`TRANSIENT`, `PERMANENT`, `RESOURCE_EXHAUSTION`, `UNKNOWN`) — provided for callers that want to categorize exceptions inside `on_after_dispatch` (e.g., a circuit breaker that only counts transient failures toward its open threshold). The library doesn't auto-categorize; callers map exceptions themselves.
21
+
22
+
-**`AgentConfig.timeout_name`** — optional human-readable label for the configured timeout, surfaced in span attributes and `TimeoutError` messages emitted by the retry loop. Has no effect on enforcement; observability tooling (e.g. Logfire) uses it to distinguish which configured timeout fired without parsing call-site context.
23
+
24
+
#### Documentation
25
+
- Updated `docs/learn/6-when-things-go-wrong.md` with a Dispatch Hooks section showing the breaker pattern.
26
+
- Updated error-hierarchy diagrams in `docs/api/index.md`, `docs/learn/6-when-things-go-wrong.md`, and `CLAUDE.md` to include `DispatchSkippedError`.
27
+
- Updated `docs/guides/fastro-agent.md` configuration reference table with `timeout_name`, `on_before_dispatch`, `on_after_dispatch`.
|`temperature`|`0.7`| Sampling temperature (0.0-2.0) |
69
69
|`max_tokens`|`4096`| Maximum response tokens |
70
70
|`timeout`|`None`| Per-request timeout in seconds. When set, forwarded to `ModelSettings.timeout` and enforced by the underlying model client. `None` means use the client's own default (typically 600s read on OpenAI). |
71
+
|`timeout_name`|`None`| Human-readable label for this timeout, surfaced in span attributes and `TimeoutError` messages emitted by the retry loop. Has no effect on enforcement — observability only. |
71
72
|`max_retries`|`3`| Retry attempts on failure |
73
+
|`on_before_dispatch`|`None`| Async callback fired before each `agent.run()` attempt inside the retry loop. Raise `DispatchSkippedError` to short-circuit (no retry, no `on_after_dispatch`). Use for circuit breakers, kill switches, rate limiters. Lives on `FastroAgent`, not `AgentConfig`. |
74
+
|`on_after_dispatch`|`None`| Async callback fired after each attempt with the exception (or `None` on success). Use for breaker recording, retry-budget tracking. Does not fire on pre-flight rejections like `CostBudgetExceededError`. Lives on `FastroAgent`, not `AgentConfig`. |
72
75
73
76
Model names use PydanticAI's provider prefix format: `openai:gpt-4o`, `anthropic:claude-3-5-sonnet`, `google:gemini-1.5-pro`. The prefix tells PydanticAI which API client to use.
├── StepExecutionError # Step failed during execution
90
-
└── CostBudgetExceededError # Budget limit hit
90
+
├── CostBudgetExceededError # Budget limit hit
91
+
└── DispatchSkippedError # Short-circuit a dispatch (breaker open, kill switch)
91
92
```
92
93
93
94
Catch `FastroAIError` to handle all library-specific errors:
@@ -125,6 +126,32 @@ except CostBudgetExceededError as e:
125
126
print(f"Step: {e.step_id}")
126
127
```
127
128
129
+
## Dispatch Hooks
130
+
131
+
Some failures are best handled before they happen. If your downstream provider is rate-limiting you, retrying ten times in a row just makes it worse. If a circuit breaker has tripped, the next request shouldn't even be attempted. `FastroAgent` accepts two async callbacks that fire **per attempt** inside the retry loop:
132
+
133
+
```python
134
+
asyncdefbefore():
135
+
if breaker.is_open():
136
+
raise DispatchSkippedError("breaker open")
137
+
138
+
asyncdefafter(exc: Exception|None):
139
+
if exc isNone:
140
+
breaker.record_success()
141
+
else:
142
+
breaker.record_failure(categorize(exc))
143
+
144
+
agent = FastroAgent(
145
+
model="openai:gpt-4o",
146
+
on_before_dispatch=before,
147
+
on_after_dispatch=after,
148
+
)
149
+
```
150
+
151
+
Raising `DispatchSkippedError` from `on_before_dispatch` short-circuits the dispatch entirely — the retry loop propagates the error without retrying, and `on_after_dispatch` is not called. Use it for guards that should fail fast: circuit breakers, kill switches, rate limiters that have already exhausted budget. Pre-flight rejections like `CostBudgetExceededError` skip both hooks (they raise before the dispatch loop is entered).
152
+
153
+
For categorizing exceptions inside `on_after_dispatch`, the library exports `ErrorCategory` — a StrEnum with `TRANSIENT`, `PERMANENT`, `RESOURCE_EXHAUSTION`, and `UNKNOWN`. Callers map exceptions themselves; the library doesn't auto-categorize.
154
+
128
155
## Designing for Failure
129
156
130
157
The goal isn't preventing all failures - it's ensuring failures don't cascade.
0 commit comments