> ## Documentation Index
> Fetch the complete documentation index at: https://docs.invokehq.run/llms.txt
> Use this file to discover all available pages before exploring further.

# reconciliation

> When a tool call returns an unknown outcome, Invoke checks live state before allowing a retry — preventing duplicate charges, records, and messages.

Reconciliation is how Invoke resolves ambiguous tool call outcomes without retrying blindly. When a tool call times out, the response is dropped, or the network fails, Invoke checks live source-of-truth state before deciding whether to allow a retry.

## Why reconciliation exists

When an API call returns an unknown outcome, the agent faces a dilemma:

* **Retry** → risk a duplicate side effect if the first attempt already succeeded
* **Don't retry** → risk leaving the action incomplete if the first attempt failed

Neither is safe without knowing what actually happened. Reconciliation solves this by checking live state first.

## How it works

<Steps>
  <Step title="Unknown outcome detected">
    A tool call returns an ambiguous result — a timeout, a dropped connection, or an HTTP 5xx with no confirmation.

    Invoke marks the execution as `unknown` and does not allow a retry yet.
  </Step>

  <Step title="Live state check">
    Invoke calls `POST /outcomes/reconcile` with the action context and a snapshot of current live state.

    For a Stripe charge, that means querying whether the charge already exists on the customer. For a CRM write, that means fetching the current record. For a Linear issue, that means checking whether the title already exists.
  </Step>

  <Step title="Decision returned">
    Invoke returns one of three decisions:

    | Decision        | Meaning                                                          |
    | --------------- | ---------------------------------------------------------------- |
    | `do_not_retry`  | Live state confirms the action already succeeded. Retry blocked. |
    | `retry_allowed` | Live state confirms the action did not succeed. Retry is safe.   |
    | `replan`        | Live state has drifted from assumed state. Agent should replan.  |
  </Step>
</Steps>

## Example: Stripe charge timeout

```typescript theme={null}
const result = await invoke.call({
  tool: "stripe.charge_customer",
  params: { customer_id: "cust_123", amount: 2400 },
  agentId: "billing-agent",
  idempotencyKey: "charge:cust_123:2400"
})

// result.status === "reconciled"
// result.final_outcome === "already_succeeded"
// result.retry === "blocked"
```

The agent never needs to implement the reconciliation logic. Invoke handles it automatically for any tool classified as `write-once-checkable`.

## Calling the reconcile endpoint directly

```bash theme={null}
curl -X POST https://api.invokehq.run/outcomes/reconcile \
  -H "X-API-Key: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "action": {
      "intent": "charge_customer",
      "tool": "stripe.charge_customer",
      "params": { "customer_id": "cust_123", "amount": 2400 }
    },
    "outcome": "UNKNOWN",
    "current_state": { "charged": true, "customer_id": "cust_123", "amount": 2400 },
    "conditions": { "charged": true }
  }'
```

## Next steps

* [Execution Records](/concepts/execution-records) — the commit record that makes reconciliation possible
* [Tool Safety Classes](/concepts/tool-safety-classes) — which tools trigger reconciliation
* [POST /outcomes/reconcile](/api/outcomes-reconcile) — full API reference
