Skip to content

bon-335: W3.2 dispatch transfer — execute_with_retry + TierGate + backends#11

Merged
Antawari merged 2 commits into
v0.1from
antawari/bon-335-dispatch-transfer
Apr 18, 2026
Merged

bon-335: W3.2 dispatch transfer — execute_with_retry + TierGate + backends#11
Antawari merged 2 commits into
v0.1from
antawari/bon-335-dispatch-transfer

Conversation

@Antawari
Copy link
Copy Markdown
Contributor

Summary

Wave 3.2 of the public v0.1 transfer. Lifts bonfire.dispatch from private V1 — the retry/tier/backend layer that wraps every agent execution.

Modules

  • dispatch/runner.pyexecute_with_retry (deterministic exponential backoff, terminal-error frozenset, event emission, never raises)
  • dispatch/tier.pyTierGate (stateless; returns True in v0.1 — full tier routing defers to W7)
  • dispatch/result.pyDispatchResult (frozen Pydantic, 4 fields: envelope, duration_seconds, retries, cost_usd)
  • dispatch/sdk_backend.pyClaudeSDKBackend (Claude Agent SDK backend)
  • dispatch/pydantic_ai_backend.pyPydanticAIBackend (lazy import; loads without pydantic_ai installed)

Locked retry algorithm

  • Terminal errors (frozenset[str]): {AgentError, RateLimitError, config, CLINotFoundError, executor} — no retry.
  • Deterministic backoff: delay = retry_delay * (2 ** attempt_index), no jitter.
  • No sleep before first attempt, no sleep after final exhausted attempt.
  • retries counts retries (not attempts); retries == 0 for first-attempt success.
  • execute_with_retry NEVER raises; infrastructure exhaustion → ErrorDetail(error_type="infrastructure", ...).
  • Events: one DispatchStarted, N DispatchRetry (N == result.retries), terminal = DispatchCompleted XOR DispatchFailed.

TDD provenance

Phase Branch SHA Scope
Knight-A (resilience lens) antawari/bon-335-knight-a 0853e04 137 tests — retry bounds, exception categorization, consumer isolation, event cardinality
Knight-B (fidelity lens) antawari/bon-335-knight-b 139c7ee 92 tests — mirror V1 test_dispatch_runner/tier/sdk_backend/pydantic_ai_backend
Sage synthesis antawari/bon-335-sage f310f0c 151 canonical tests across 5 files; 5 scope decisions
Warrior (conservative) antawari/bon-335-warrior 846919c 570 LOC minimal impl, V1 verbatim modulo adaptations

5 scope adjudications (Sage decisions)

  1. pydantic_ai_backend: KEEP — 77 LOC, module-level Agent: Any = None sentinel, lazy import inside execute(). Seals AgentBackend protocol as provider-agnostic.
  2. dispatch_wiring: CUT — V1's wiring tests target bonfire.cli.commands.dispatch + XPTracker composition. None exist in public v0.1; CLI is flat.
  3. resolve_llm_backend: OUT — grep confirms zero matches in V1's src/bonfire/dispatch/; resolver lives in cartographer, not dispatch.
  4. Jitter: NONE — V1's retry delay is deterministic; canonical asserts lower bounds.
  5. Budget short-circuit: NONE — V1 runner has no cross-attempt budget read; enforced by SDK + engine, not dispatch.

Three adaptations from private V1

  1. Private refs scrubbed.
  2. Public v0.1 imports throughout.
  3. Axiom-load paths removed from sdk_backend; _TERMINAL_ERROR_TYPES has 5 entries (V1 has 6 — axiom_load dropped because there's no compiler interface in v0.1).

Verification

  • Full suite: 639 passing (488 baseline + 151 new)
  • Ruff: scoped + full-tree clean (54 files)

Gate tier

Infra (per docs/release-gates.md).

Sage decisions doc: docs/audit/sage-decisions/bon-335-sage-2026-04-18T03-42-14Z.md.

Dual-lens pre-merge review follows.

Closes BON-335.

🤖 Generated with Claude Code

Antawari and others added 2 commits April 17, 2026 22:09
…/PydanticAI backends

Lift bonfire.dispatch from private V1 into public v0.1. Retry engine,
tier gating, dispatch results, two AgentBackend implementations.
Infra-tier gate.

Modules:
- dispatch/__init__.py — re-exports (5 names)
- dispatch/result.py — DispatchResult (frozen Pydantic, 4 fields)
- dispatch/tier.py — TierGate (stateless, returns True in v0.1)
- dispatch/runner.py — execute_with_retry (deterministic backoff,
  never raises, event emission, terminal-error frozenset)
- dispatch/sdk_backend.py — ClaudeSDKBackend (axiom-load paths removed)
- dispatch/pydantic_ai_backend.py — PydanticAIBackend (lazy import)

TDD provenance:
- Knight-A antawari/bon-335-knight-a @ 0853e04: 137 tests, resilience lens
- Knight-B antawari/bon-335-knight-b @ 139c7ee: 92 tests, fidelity lens
- Sage antawari/bon-335-sage @ f310f0c: 151 canonical tests, 5 scope
  decisions (pydantic_ai kept, dispatch_wiring cut, no resolver, no
  jitter, no budget short-circuit in dispatch)
- Warrior antawari/bon-335-warrior @ 846919c: 570 LOC minimal impl

Three adaptations from private V1:
1. Private refs scrubbed (no BON-NNN, no bonfire-beta#113).
2. Public v0.1 imports (AgentBackend/DispatchOptions from bonfire.protocols;
   events from bonfire.models.events).
3. Axiom-load paths removed from sdk_backend (no compiler interface in v0.1);
   _TERMINAL_ERROR_TYPES has 5 entries (V1 has 6; axiom_load dropped).

Sage decisions doc: docs/audit/sage-decisions/bon-335-sage-2026-04-18T03-42-14Z.md

Verification: 639 tests passing (488 baseline + 151 new). Ruff clean.

Closes BON-335.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wizard + code-reviewer parallel pass. Both APPROVE. Non-blocking findings only:
  - Wizard: 1 NIT (Sage __all__ doc drift)
  - code-reviewer: 0 critical / 0 high / 2 low / 6 info

Invariants confirmed: 639 tests green, ruff clean, scrub clean,
_TERMINAL_ERROR_TYPES=5 (axiom_load correctly dropped), DispatchResult frozen,
public v0.1 imports correct, retry algorithm V1-identical.
@Antawari
Copy link
Copy Markdown
Contributor Author

Dual-Lens Pre-Merge Review — APPROVED

Both lenses merge-approve. Squash-merging.

Verdicts

Reviewer Lens Verdict Tests Ruff Findings
Wizard Fidelity / Design APPROVE 639 ✓ clean 1 NIT
code-reviewer Engineering Discipline APPROVE (MERGE) 639 ✓ clean 0 crit / 0 high / 2 low / 6 info

Invariants Confirmed

  • Tests: 639/639 pass (488 baseline + 151 new dispatch — collected exactly per Sage §5: 24+22+63+27+15)
  • Ruff: clean on src/ + tests/
  • Scrub: zero BON-\d+, bonfire-beta, or constraint-index.md in src/tests
  • _TERMINAL_ERROR_TYPES: runtime-inspected = exactly 5 entries (AgentError, CLINotFoundError, RateLimitError, config, executor) — axiom_load correctly dropped (no compiler interface in v0.1)
  • DispatchResult frozen: programmatically verified, mutation raises ValidationError; Envelope typed directly, no Any drift
  • Public v0.1 imports: AgentBackend/DispatchOptions from bonfire.protocols, events from bonfire.models.events, zero AxiomLoadFailed leakage
  • Retry algorithm byte-identical to V1: delay = retry_delay * (2**attempt), no pre-first-attempt sleep, no post-exhaustion sleep, retries counts retries (not attempts)
  • Async discipline: asyncio.timeout + aclosing; consumer/handler isolation with logger.exception wrapping per-handler; EventBus owns sequence stamping (dispatch just emits)
  • SDK integration: ClaudeSDKBackend uses claude_agent_sdk; PydanticAIBackend lazy-imports via Agent: Any = None sentinel — module loads without pydantic_ai installed
  • TierGate: stateless, kw-only-named callsites, stable across unicode/whitespace/long-string matrix
  • PR metadata: base v0.1, commit cites BON-335, Sage decisions in diff

Non-Blocking Follow-Ups (file post-merge)

  1. Wizard NIT — Sage §2.3 locks __all__ to 3 symbols; shipped __init__.py exports backends as convenience re-exports. Test uses subset assertion, so this is a valid superset — Sage doc drift only.
  2. ENG-01 (low)tests/unit/test_dispatch_tier.py:15 docstring declares check_tier kw-only (*, ...), but impl (tier.py:12-23) uses positional-or-keyword. Every caller uses keyword form, so no behavior gap. Align docstring, or add *, + negative test.
  3. ENG-02 (low, observability)runner.py:220-234 exception-exhaustion rebuilds envelope with error_type='infrastructure', overwriting any earlier richer error_type. Spec-locked (Sage §6, V1-verbatim); worth a follow-up ticket to preserve the root-cause type.
  4. Six info-level code-reviewer notes (all deferrable).

Audit Trail

  • docs/audit/retrospective/wizard-20260418T172340Z.{json,md} (committed this branch)
  • docs/audit/retrospective/code-reviewer-20260418T172251Z.{json,md} (committed this branch)
  • docs/audit/sage-decisions/bon-335-sage-*.md (already in diff)

Gate

Infra-tier gate: CLEARED. Merging via squash.

@Antawari Antawari merged commit 746be45 into v0.1 Apr 18, 2026
@Antawari Antawari deleted the antawari/bon-335-dispatch-transfer branch April 18, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant