---
id: CAP-PFM-LLM-002
title: Bound AI conversations and handle recoverable failures
feature: FEAT-PFM-LLM-01
product_area: platform
module: llm
revision: 1
status: draft
product_intent: proposed
implementation_evidence: observed
supersedes: []
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
intent_sources: []
purpose: Keep an AI conversation accountable while giving recoverable failures a controlled route forward.
outcomes:
  - 'Conversation guards restrict ordinary turns, tool batches, time and credits.'
  - Retryable provider faults receive bounded retries.
  - Attempt records retain provider usage and pricing information.
concepts:
  - CON-PFM-LLM-001
  - CON-PFM-LLM-002
actors:
  - ACT-PFM-032
  - ACT-PFM-034
surfaces:
  - api
  - job
preconditions:
  - Admission has reserved capacity for the request.
postconditions:
  - The conversation records its outcome and provider attempts.
permissions: []
rules:
  - id: BR-CAP-PFM-LLM-002-01
    proved_by: [ATS-PFM-LLM-001-11, ATS-PFM-LLM-002-01, ATS-PFM-LLM-002-12, TST-PFM-LLM-020, TST-PFM-LLM-068]
    statement: >-
      A conversation without an accepted answer ends when it has used the permitted turns, after sending that
      many model requests.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/llm/tests/unit/runtime/run-tool-loop.spec.ts#stops a conversation that will
      not converge
    origin: none
  - id: BR-CAP-PFM-LLM-002-02
    proved_by: [ATS-PFM-LLM-002-02, TST-PFM-LLM-068]
    statement: >-
      Without an accepted final answer, an oversized tool batch ends the conversation before any handler in
      that batch runs.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/llm/tests/unit/runtime/run-tool-loop.spec.ts#runs no handler for the turn it
      refused, rather than half of it
    origin: none
  - id: BR-CAP-PFM-LLM-002-03
    proved_by: [ATS-PFM-LLM-002-03, TST-PFM-LLM-068]
    statement: >-
      After checking final answers and terminal responses, a tool-requesting turn stops further work when
      accumulated rounded turn costs are greater than the hold; spending exactly the hold continues and the
      crossing turn remains billable.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/llm/runtime/run-tool-loop.ts:runToolLoop'
    origin: none
  - id: BR-CAP-PFM-LLM-002-04
    proved_by: [ATS-PFM-LLM-002-04, TST-PFM-LLM-068]
    statement: >-
      Before starting another turn, the loop refuses to send when no total time remains and caps the provider
      attempt timeout at the remaining conversation time.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/llm/runtime/run-tool-loop.ts'
    origin: none
  - id: BR-CAP-PFM-LLM-002-05
    proved_by: [ATS-PFM-LLM-002-05, ATS-PFM-LLM-002-14, TST-PFM-LLM-065, TST-PFM-LLM-067]
    statement: >-
      Timeouts, ordinary rate limits, overload, server faults, broken connections and aborts can retry with
      increasing waits and a default of three attempts; malformed or unauthorized provider requests do not
      retry.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/llm/runtime/execute-with-llm-retry.ts'
    origin: none
  - id: BR-CAP-PFM-LLM-002-06
    proved_by: [ATS-PFM-LLM-002-06, TST-PFM-LLM-067]
    statement: >-
      A provider rate limit that identifies a depleted Rhythm balance is classified as credit exhaustion and
      does not retry.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/llm/tests/unit/runtime/retry-policy.spec.ts#is not retried, because retrying
      it reads a purchase problem as load
    origin: 'Tribal TRI-1899 (services-platform:docs/llm.md#The runtime)'
  - id: BR-CAP-PFM-LLM-002-07
    proved_by: [ATS-PFM-LLM-002-07, ATS-PFM-LLM-002-13, TST-PFM-LLM-018, TST-PFM-LLM-020]
    statement: >-
      Attempts record their turn, retry, usage and timing and receive individual prices; after 64 retained
      attempts, additional attempts increase the count and mark the record incomplete without adding rows.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/llm/services/persistence/invocations.ts:recordInvocationAttempt'
    origin: none
  - id: BR-CAP-PFM-LLM-002-08
    proved_by: []
    statement: >-
      For a nonterminal turn within budget, the model receives one combined tool-results message and every
      answered call consumes tool budget.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: observed
    source: 'services-platform:modules/llm/runtime/run-tool-loop.ts'
    origin: none
  - id: BR-CAP-PFM-LLM-002-09
    proved_by: []
    statement: >-
      A thrown tool-handler error becomes an error result for the model, so handler messages must be suitable
      to disclose to that model.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: test
    source: >-
      services-platform:modules/llm/tests/unit/runtime/run-tool-loop.spec.ts#passes the failure back to the
      model as a tool result marked in error
    origin: none
  - id: BR-CAP-PFM-LLM-002-10
    proved_by: [ATS-PFM-LLM-002-08, TST-PFM-LLM-068]
    statement: >-
      Rejected final answers return the feature’s feedback as tool errors and consume tool budget; a failing
      answer judge also becomes a rejection and further turns use the existing budgets.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/llm/runtime/run-tool-loop.ts'
    origin: none
  - id: BR-CAP-PFM-LLM-002-11
    proved_by: []
    statement: >-
      A provider refusal ends without retry, while an output-token cutoff produces an incomplete outcome
      rather than an accepted answer.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: observed
    source: 'services-platform:modules/llm/runtime/run-tool-loop.ts'
    origin: none
  - id: BR-CAP-PFM-LLM-002-12
    proved_by: [ATS-PFM-LLM-002-09, TST-PFM-LLM-064]
    statement: >-
      Stable instructions, tool definitions and explicitly stable content can receive cache markers, with
      excess markers dropped so the provider receives at most four.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/llm/tests/unit/runtime/anthropic-provider.spec.ts#never sends more than the
      four Anthropic takes, however many blocks asked to be cached
    origin: none
  - id: BR-CAP-PFM-LLM-002-13
    proved_by: []
    statement: >-
      Failures do not trigger a substitute model or provider, and consuming features cannot replace published
      routing or limits.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: 'services-platform:docs/llm/llm-foundation-and-generate-query.md#4.4 Accounting Rules'
    origin: none
  - id: BR-CAP-PFM-LLM-002-14
    proved_by: [ATS-PFM-LLM-002-10]
    statement: >-
      An accepted final answer completes before the later tool and credit checks and does not increase the
      answered-tool count.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/llm/runtime/run-tool-loop.ts:runToolLoop'
    origin: none
  - id: BR-CAP-PFM-LLM-002-15
    proved_by: [ATS-PFM-LLM-002-11]
    statement: >-
      A requested tool without a supplied handler produces a recoverable error result, allowing later turns
      within the remaining budgets.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/llm/runtime/run-tool-loop.ts:runToolLoop'
    origin: none
scenarios:
  - id: SC-CAP-PFM-LLM-002-01
    given: The first provider attempt is temporarily overloaded.
    when: A permitted retry succeeds.
    then: Both attempts remain visible and the successful usage determines the charge.
    provenance: test
    source: >-
      services-platform:modules/llm/tests/integration/lifecycle/run-operation.integration.spec.js#charges only
      what the successful attempt reported
  - id: SC-CAP-PFM-LLM-002-02
    given: The consumer rejects the first final answer.
    when: The model receives repair feedback.
    then: A later accepted answer completes the conversation within the remaining controls.
    provenance: test
    source: >-
      services-platform:modules/llm/tests/unit/runtime/run-tool-loop.spec.ts#ends on the answer the consumer
      accepted, which is the repaired one
edge_cases: []
constraints: []
depends_on:
  - CAP-PFM-LLM-001
  - CAP-PFM-LLM-005
related:
  - CAP-PFM-LLM-012
journeys: []
open_questions: []
evidence:
  - >-
    services-platform at ad3f6a6582843008ed0f5a17b131878d15829af1: docs/llm.md (The runtime, What is cached,
    Repairing a final answer); modules/llm/runtime/run-tool-loop.ts, retry-policy.ts,
    execute-with-llm-retry.ts, anthropic.ts, provider.ts; catalog/llm-operations.ts (limits);
    db/llm-invocation-attempts.table.ts; tests unit/runtime/run-tool-loop.spec.ts, retry-policy.spec.ts,
    anthropic-provider.spec.ts, integration/lifecycle/run-operation.integration.spec.js,
    executions.integration.spec.js.
controls: [TC-AI-001, TC-AI-002, TC-AI-003, TC-AI-004, TC-AI-005, TC-AI-006, TC-AI-007, TC-AVL-001, TC-AVL-004, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-006, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-VEN-001, TC-VEN-002, TC-VEN-003]
exercised_by: [FAS-PFM-LLM-01-01, FAS-PFM-LLM-01-02, FAS-PFM-LLM-01-05]
suites: [TST-PFM-LLM-018, TST-PFM-LLM-020, TST-PFM-LLM-064, TST-PFM-LLM-065, TST-PFM-LLM-067, TST-PFM-LLM-068]
---

# Bound AI conversations and handle recoverable failures

## Purpose

Keep an AI conversation accountable while giving recoverable failures a controlled route forward.

## How It Works

Platform runs the model conversation using the limits published for its operation. The model can ask for tools or offer a final answer. The consuming feature can accept that answer or return a validation message for another attempt. Ordinary tool errors become information the model can use to repair its request. Recoverable provider faults take the retry path, while permanent faults end it. Attempts retain operational details without retaining the conversation body. Budget checks occur at specific points, so an accepted answer can take precedence over later checks. The product should settle the meaning of these limits before presenting them as absolute cost or time guarantees.

## Relationships

- Run AI requests with consistent attribution and accounting supplies the prerequisite behavior for this outcome.
- Publish AI operation definitions and historical prices supplies the prerequisite behavior for this outcome.
