Skip to content

Sessions quietly finish with no answer when answer_format has an enum inside a list of objects, and no error is raised #197

Description

@arthurkatcher

Platform behavior rather than an SDK bug, but this repo seems to be the closest public tracker (same spirit as #194).

Summary

Found while building an agent on the Computer Use Agents platform — a QA reviewer whose report is a structured list of graded findings, which turns out to be exactly the schema shape that triggers this.

Sessions with an answer_format schema containing an enum field inside an object in an array — e.g. missions: [{outcome: "achieved" | "friction" | "blocked"}] — finish completed with latest_answer: null. No error, no answer_validation, nothing in the events; the failure is completely silent. Replace that one enum with a plain string and the same session returns a valid structured answer. Flat top-level enums are fine; it has to be nested in an array item, and the model has to actually emit an entry (empty arrays slip through, which makes it look intermittent).

Calling the Models API directly with the same schema shows where the answers go. With chat_template_kwargs: {"enable_thinking": true} (how agent sessions run), response_format: json_schema outputs come back corrupted at high rates — <loc_*> grounding tokens and serving artifacts leak inside string values, answers fill with tab runs, generations run away to finish_reason: length. With thinking off, the same requests are clean every time.

This looks like two problems stacked:

  1. Model serving: schema-constrained generation is unstable when the reasoning template is enabled.
  2. Agents platform: a final answer that fails schema validation is discarded silently — the session still ends completed with latest_answer: null and error_code: null, although the docs define answer_validation for exactly this case. This turns a model-side bug into an invisible one: we spent three multi-agent QA runs debugging our orchestration before finding it.

Reproduction 1 — agents platform (deterministic)

Two sessions, EU region, hai-agents 1.0.7, identical except one field type:

from hai_agents import Client

FAILS = {  # enum inside array item -> latest_answer is null
    "type": "object",
    "properties": {
        "answer": {"type": "string"},
        "items": {"type": "array", "items": {
            "type": "object",
            "properties": {"outcome": {"type": "string", "enum": ["achieved", "friction", "blocked"]}},
            "required": ["outcome"]}}},
    "required": ["answer", "items"]}

# PASSES: same schema with {"type": "string"} instead of the enum member.

client = Client()
r = client.run_session(
    agent={"name": "repro", "description": "answer_format repro",
           "environments": ["h/textual_browser"], "model": "holo3-122b-a10b",
           "instructions": "Do not browse. Answer in the required structured format with exactly one entry in the list.",
           "skills": ["h/answering"], "answer_format": FAILS},
    messages="What is 2+2?")
print(r.status, r.answer)   # completed None   <- silent
  • Failing session: 31e2b6e5-229c-48b9-9485-25e62e4aaac8completed, error_code: null, latest_answer: null
  • Passing session (string instead of enum): f8890af1-4d67-416b-a037-adc64b180c60 — valid structured answer

Bisected across ~18 sessions: subagents, custom tools, $ref/$defs, nullable fields, integer fields, schema size, and long descriptions were each eliminated; top-level enums pass; the failure requires the enum nested in an array item and the model actually emitting an entry (empty arrays slip through). The schema is standard JSON Schema; gpt-4o-mini under OpenAI strict mode fills it correctly.

Reproduction 2 — Models API (probabilistic, shows the mechanism)

Same model as the platform sessions (holo3-122b-a10b), response_format: {type: json_schema, strict: true}, schema above, 3 samples per cell:

  • enable_thinking: true: 5/6 samples corrupted (across enum and string variants). Actual message.content values:

    {"answer": "<loc_0><loc_0><loc_500...            <- GUI grounding tokens leaking into a string value
    {"answer": "../../../../../../tmp/tmpe0k118qz"   <- temp-file path surfacing in the answer
    {"answer": "\t\t\t\t\t... (~50 tabs)             <- tab flood, ends with finish_reason: length
    
  • enable_thinking: false: 3/3 clean, parseable, semantically correct.

  • holo3-1-35b-a3b shows the same pattern; worst case there leaked a markdown fence into a string value:

    {"answer": "}```json\n{ "  , "items": ...
    

The reasoning template is how agent sessions run, so the platform's answer action operates in the corrupting configuration. Enum-in-array is the construct where corruption crosses into hard validation failure most reliably (deterministically so on the platform, per Repro 1).

Expected

Either a schema-valid final answer, or a session that fails loudly: error_code: answer_validation (as documented), ideally with the rejected raw text attached for debugging.

Actual

status: completed, latest_answer: null, error_code: null. Nothing in the session events indicates an answer was attempted and rejected.

Environment

  • hai-agents 1.0.7 (Python), EU region (agp.eu.hcompany.ai)
  • Platform sessions and Models API repro: holo3-122b-a10b (mechanism also reproduced on holo3-1-35b-a3b)
  • Dates: 2026-07-18, session IDs above should be retrievable in your logs

Workaround (for anyone else who hits this)

Avoid enums inside array items: make the field a plain string and list the allowed values in its description (the passing session above is exactly this one-field swap). answer_format otherwise keeps working.

Since the corruption on the Models API wasn't strictly limited to enum schemas, for our own flow we went one step further and described the JSON shape in the agent's instructions without answer_format, validating client-side — either approach gets you unblocked.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions