microsoft / microsoft/agent-framework
Python: unknown-call termination leaves service-managed conversations unresolved
@eavanvalkenburg is already working on this.
Since Sep 7, 2026.
- Dominant language
- Python
- Stars
- 13.6k
- Forks
- 2.3k
- Avg merge
- 2d 45m
- Merged PRs (30d)
- 358
Description
### Description
With `terminate_on_unknown_calls=True`, an unknown local function call raises `KeyError` after a service-managed continuation has already been persisted, but the function-calling loop does not settle the unresolved server-side call before propagating the exception.
This leaves the session pointing at a hosted response whose function call has no terminal output. Response-ID and similar service-managed continuations can reject the next request over that session because the prior call remains unresolved.
The behavior is present on current `main` at `2c49f50cf08ebb6c1687146336f039051f159333`.
### Root cause
Both non-streaming and streaming loops update continuation state immediately after the model response:
```python
self._update_function_invocation_continuation_state(...)
```
They settle dangling calls only when `_process_model_function_calls(...)` raises `MiddlewareFailure`:
```python
try:
function_processing = await _process_model_function_calls(...)
except MiddlewareFailure:
await self._settle_dangling_service_function_calls(...)
raise
```
Unknown-call termination is raised separately as a plain `KeyError` inside `_try_execute_function_call_groups(...)`, so it bypasses that settlement path.
### Reproduction
```python
client = MockBaseChatClient()
@tool(name="known_func")
def known_func(value: str) -> str:
return value
client.function_invocation_configuration["terminate_on_unknown_calls"] = True
client.run_responses = [
ChatResponse(
messages=Message(
role="assistant",
contents=[
Content.from_function_call(
call_id="u1",
name="unknown_func",
arguments={},
),
Content.from_function_call(
call_id="k1",
name="known_func",
arguments={"value": "x"},
),
],
),
conversation_id="conv_123",
)
]
session = AgentSession()
agent = Agent(client=client, tools=[known_func])
with pytest.raises(KeyError, match="unknown_func"):
await agent.run("run", session=session)
assert session.service_session_id == "conv_123"
assert client.call_count == 2 # fails: actual value is 1
```
Observed:
```text
exception=KeyError:'Error: Requested function "unknown_func" not found.'
conversation_id=conv_123
call_count=1
```
The same missing settlement boundary exists in the streaming loop.
### Expected behavior
The configured fail-closed `KeyError` should still reach the caller, but a service-managed conversation must first receive one terminal error result per unresolved local function call, with tool calling disabled, just as it does for `MiddlewareFailure`.
Sessions without a service-managed continuation should not incur an extra model request.
### Implementation considerations
A focused fix should:
- distinguish unknown-call termination from unrelated `KeyError` exceptions;
- settle the original unresolved batch in both streaming and non-streaming paths;
- preserve the public `KeyError` behavior;
- advance response-ID continuation state to the settlement response;
- keep settlement best-effort so settlement failure never masks the original abort;
- cover both service-managed and ordinary sessions.
This should remain separate from mixed-batch precedence work: the defect already reproduces with an ordinary unknown-call batch on `main`.
I can prepare a focused fix and regression coverage after the core team confirms the intended exception/settlement boundary.
AI assistance was used for call-path analysis and drafting. The reproduction above was executed against an isolated checkout of the cited `main` commit.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.