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
Introduce an optional SimulateServiceStoredChatHistory setting to persist chat history after each individual service call within the FIC loop, matching the AI service's behavior. Trailing `FunctionResultContent` trimming is unnecessary with this approach (it is naturally handled).
62
+
Introduce an optional RequirePerServiceCallChatHistoryPersistence setting to persist chat history after each individual service call within the FIC loop, matching the AI service's behavior. Trailing `FunctionResultContent` trimming is unnecessary with this approach (it is naturally handled).
- Good, because the stored history matches the service's behavior when opting in for both timing and content, fully satisfying driver A.
68
68
- Good, because intermediate progress is preserved if the process is interrupted, satisfying driver C.
@@ -73,36 +73,49 @@ Settings:
73
73
74
74
## Decision Outcome
75
75
76
-
Chosen option: **Option 2: Opt-in per-service-call persistence (via `SimulateServiceStoredChatHistory`)**. The existing per-run persistence behavior is retained as-is, requiring no changes from users. Per-service-call persistence is available as an opt-in feature via the `SimulateServiceStoredChatHistory` setting. This satisfies drivers B (atomicity) and D (simplicity) for the common case, while fully satisfying driver A (consistency) for users who opt into simulated service-stored behavior. Users who need per-service-call persistence for recoverability (driver C) can enable it explicitly.
76
+
Chosen option: **Option 2: Opt-in per-service-call persistence (via `RequirePerServiceCallChatHistoryPersistence`)**. The existing per-run persistence behavior is retained as-is, requiring no changes from users. Per-service-call persistence is available as an opt-in feature via the `RequirePerServiceCallChatHistoryPersistence` setting. This satisfies drivers B (atomicity) and D (simplicity) for the common case, while fully satisfying driver A (consistency) for users who opt into simulated service-stored behavior. Users who need per-service-call persistence for recoverability (driver C) can enable it explicitly.
77
77
78
78
### Configuration Matrix
79
79
80
-
The behavior depends on the combination of `UseProvidedChatClientAsIs` and `SimulateServiceStoredChatHistory`:
80
+
The behavior depends on the combination of `UseProvidedChatClientAsIs` and `RequirePerServiceCallChatHistoryPersistence`:
|`false` (default) |`false` (default) |**Per-run persistence.** Messages are persisted at the end of the full agent run via the `ChatHistoryProvider`. |
85
-
|`false`|`true`|**Per-service-call persistence (simulated).** A `ServiceStoredSimulatingChatClient` middleware is automatically injected into the chat client pipeline between `FunctionInvokingChatClient` and the leaf `IChatClient`. Messages are persisted after each service call. A sentinel `ConversationId` causes FIC to treat the conversation as service-managed. |
85
+
|`false`|`true`|**Per-service-call persistence (simulated).** A `PerServiceCallChatHistoryPersistingChatClient` middleware is automatically injected into the chat client pipeline between `FunctionInvokingChatClient` and the leaf `IChatClient`. Messages are persisted after each service call. A sentinel `ConversationId` causes FIC to treat the conversation as service-managed. |
86
86
|`true`|`false`|**Per-run persistence.** No middleware is injected because the user has provided a custom chat client stack. Messages are persisted at the end of the run. |
87
-
|`true`|`true`|**User responsibility.** The system checks whether the custom chat client stack includes a `ServiceStoredSimulatingChatClient`. If not, a warning is emitted — the user is expected to have added their own per-service-call persistence mechanism. End-of-run persistence is skipped. |
87
+
|`true`|`true`|**User responsibility.** The system checks whether the custom chat client stack includes a `PerServiceCallChatHistoryPersistingChatClient`. If not, a warning is emitted — the user is expected to have added their own per-service-call persistence mechanism. End-of-run persistence is skipped. |
88
88
89
89
### Consequences
90
90
91
91
- Good, because per-run persistence is atomic by default — chat history is only updated when the full run succeeds, satisfying driver B.
92
92
- Good, because the default mental model is simple: one run = one history update, satisfying driver D.
93
-
- Good, because users who opt into `SimulateServiceStoredChatHistory` get stored history that matches the service's behavior for both timing and content, fully satisfying driver A.
93
+
- Good, because users who opt into `RequirePerServiceCallChatHistoryPersistence` get stored history that matches the service's behavior for both timing and content, fully satisfying driver A.
94
94
- Good, because per-service-call persistence preserves intermediate progress if the process is interrupted, satisfying driver C when opted in.
95
95
- Good, because no separate `FunctionResultContent` trimming logic is needed when per-service-call persistence is active — it is naturally handled.
96
96
- Good, because conflict detection (configurable via `ThrowOnChatHistoryProviderConflict`, `WarnOnChatHistoryProviderConflict`, `ClearOnChatHistoryProviderConflict`) prevents misconfiguration when a service returns a `ConversationId` alongside a configured `ChatHistoryProvider`.
97
97
- Bad, because per-service-call persistence (when opted in) may leave chat history in an incomplete state if the run fails mid-loop (e.g., `FunctionCallContent` stored without corresponding `FunctionResultContent`), requiring manual recovery in rare cases.
98
-
- Neutral, because users who want per-service-call consistency can opt in via `SimulateServiceStoredChatHistory = true`, satisfying driver E.
98
+
- Neutral, because users who want per-service-call consistency can opt in via `RequirePerServiceCallChatHistoryPersistence = true`, satisfying driver E.
99
99
- Neutral, because increased write frequency from per-service-call persistence may impact performance for some storage backends; this can be mitigated with a caching decorator.
100
100
101
101
### Implementation Notes
102
102
103
103
#### Conversation ID Consistency
104
104
105
-
We should introduce a separate `ConversationIdPersistingChatClient`, middleware which allows us to
106
-
persist response `ConversationIds` during the FICC loop. This could be used with or without
107
-
`ServiceStoredSimulatingChatClient`.
105
+
When `RequirePerServiceCallChatHistoryPersistence` is enabled, the `PerServiceCallChatHistoryPersistingChatClient`
106
+
decorator also updates `session.ConversationId` after each service call. This handles two scenarios:
107
+
108
+
1.**Framework-managed chat history** — the decorator sets a sentinel `ConversationId` on the response
109
+
so that `FunctionInvokingChatClient` treats the conversation as service-managed (clearing accumulated
110
+
history between iterations and not injecting duplicate `FunctionCallContent` during approval processing).
111
+
112
+
2.**Service-stored chat history** — when the service returns a real `ConversationId`, the decorator
113
+
updates `session.ConversationId` immediately after each service call, rather than deferring the update
114
+
to the end of the run. This ensures intermediate ConversationId changes are captured even if the
115
+
process is interrupted mid-loop.
116
+
117
+
For some service-stored scenarios (e.g., the Conversations API with the Responses API), there is only
118
+
one thread with one ID, so every service call returns the same ConversationId and this per-call update
119
+
makes no practical difference. Enabling `RequirePerServiceCallChatHistoryPersistence` ensures consistent
120
+
per-service-call behavior across all service types regardless of how they manage ConversationIds.
Copy file name to clipboardExpand all lines: dotnet/samples/02-agents/Agents/Agent_Step19_InFunctionLoopCheckpointing/README.md
+6-6Lines changed: 6 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,19 +1,19 @@
1
1
# In-Function-Loop Checkpointing
2
2
3
-
This sample demonstrates how `ChatClientAgent` can persist chat history after each individual call to the AI service using the `SimulateServiceStoredChatHistory` option. This per-service-call persistence ensures intermediate progress is saved during the function invocation loop.
3
+
This sample demonstrates how `ChatClientAgent` can persist chat history after each individual call to the AI service using the `RequirePerServiceCallChatHistoryPersistence` option. This per-service-call persistence ensures intermediate progress is saved during the function invocation loop.
4
4
5
5
## What This Sample Shows
6
6
7
-
When an agent uses tools, the `FunctionInvokingChatClient` loops multiple times (service call → tool execution → service call → …). By enabling `SimulateServiceStoredChatHistory = true`, chat history is persisted after each service call via the `ServiceStoredSimulatingChatClient` decorator:
7
+
When an agent uses tools, the `FunctionInvokingChatClient` loops multiple times (service call → tool execution → service call → …). By enabling `RequirePerServiceCallChatHistoryPersistence = true`, chat history is persisted after each service call via the `PerServiceCallChatHistoryPersistingChatClient` decorator:
8
8
9
-
- A `ServiceStoredSimulatingChatClient` decorator is inserted into the chat client pipeline
9
+
- A `PerServiceCallChatHistoryPersistingChatClient` decorator is inserted into the chat client pipeline
10
10
- Before each service call, the decorator loads history from the `ChatHistoryProvider` and prepends it to the request
11
11
- After each service call, the decorator notifies the `ChatHistoryProvider` (and any `AIContextProvider` instances) with the new messages
12
12
- Only **new** messages are sent to providers on each notification — messages that were already persisted in an earlier call within the same run are deduplicated automatically
13
13
14
-
By default (without `SimulateServiceStoredChatHistory`), chat history is persisted at the end of the full agent run instead. To use per-service-call persistence, set `SimulateServiceStoredChatHistory = true` on `ChatClientAgentOptions`.
14
+
By default (without `RequirePerServiceCallChatHistoryPersistence`), chat history is persisted at the end of the full agent run instead. To use per-service-call persistence, set `RequirePerServiceCallChatHistoryPersistence = true` on `ChatClientAgentOptions`.
15
15
16
-
With `SimulateServiceStoredChatHistory` = true, the behavior matches that of chat history stored in the underlying AI service exactly.
16
+
With `RequirePerServiceCallChatHistoryPersistence` = true, the behavior matches that of chat history stored in the underlying AI service exactly.
17
17
18
18
Per-service-call persistence is useful for:
19
19
-**Crash recovery** — if the process is interrupted mid-loop, the intermediate tool calls and results are already persisted
@@ -29,7 +29,7 @@ The sample asks the agent about the weather and time in three cities. The model
0 commit comments