Files
james.maes 219532f468 feat(04-04): AgentOrchestrator actor + submit/cancelAndSubmit (AGENT-06)
- AgentOrchestrator actor: turn-lifecycle state machine; LLMProvider
  protocol-driven; injection via providerFactory closure for testability
- submit(_:) returns .ran or .rejected(.turnInFlight)
- cancelAndSubmit(_:) returns .superseded(priorId, .bargedIn); critical
  AGENT-06 actor-reentrancy guard: priorTask.cancel() THEN
  `_ = await priorTask.value` BEFORE assigning new currentTurn (prevents
  late .messageStop from cancelled stream from clobbering currentTurn)
- ReplayLog wired: turnNonce persisted via startTurn(turnNonce:); full
  blob via .toolResultFull (AGENT-08); SEC-06 invariant (nonce never
  appears in OrchestratorEvent emitted to bus)
- ToolResultPacker integration: 8 KB cap on model-facing string (AGENT-08);
  full bytes preserved for replay via separate channel
- Tool-use loop: append assistant tool_use msg, dispatch via
  ToolDispatcher, append wrapped tool_result msg, re-invoke provider
- Architectural deviation (Rule 3 — blocking dep cycle): Replay already
  depends on AgentCore for TurnID; the plan's literal layout would have
  introduced AgentCore→Replay cycle. Resolved by carving a new target
  `AgentOrchestrator` in the AgentCore package, depending on both
  AgentCore and Replay. Plan files_modified paths shifted from
  `Sources/AgentCore/` to `Sources/AgentOrchestrator/` accordingly
- 11 new tests (OrchestratorSubmitTests + OrchestratorCancelTests) plus
  MockLLMProvider scriptable test double; 105 total AgentCore tests green
2026-04-24 18:16:05 -05:00

18 lines
567 B
Swift

import Foundation
import AgentCore
/// Tracks the AGENT-09 stream_truncated retry budget for one logical turn.
///
/// **Bounded at 1.** A second back-to-back truncation in the same logical
/// turn is terminal: orchestrator emits `.error(... .streamTruncatedFinal)`
/// and ends the turn. No exponential backoff, no rolling window — one shot.
struct RetryState: Sendable {
var budget: Int = 1
var originalTurnId: TurnID
init(originalTurnId: TurnID, budget: Int = 1) {
self.originalTurnId = originalTurnId
self.budget = budget
}
}