microsoft / microsoft/agent-framework

Python: unknown-call termination leaves service-managed conversations unresolved

Open
#8,093 6 comments 0 reactions 1 assignee View on GitHub

@eavanvalkenburg is already working on this.

Since Sep 7, 2026.

agents python reproduced
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.