Skip to content

fix(agent): isolate response language from connector context (toby) - #1990

Open
OliverBryant wants to merge 5 commits into
xorbitsai:mainfrom
OliverBryant:codex/fix-request-language-consistency
Open

fix(agent): isolate response language from connector context (toby)#1990
OliverBryant wants to merge 5 commits into
xorbitsai:mainfrom
OliverBryant:codex/fix-request-language-consistency

Conversation

@OliverBryant

Copy link
Copy Markdown
Contributor

Summary

  • isolate response-language selection from connector-enriched execution text
  • keep request_context.output_language as the only hard language authority
  • use the caller-provided display_message as a request-only language harness when no output language is pinned
  • apply the same guidance across root context, Auto, ReAct, DAG planning, DAG steps, DAG completion, and final-answer schemas

Root cause

The runtime can receive an execution prompt containing connector context such as sender names, email addresses, and localized metadata. Although the user's original request is English, the answering model can incorrectly treat that auxiliary text as evidence for another response language. The existing soft policy referred to the "current user request" without consistently separating the user-authored request from the enriched execution prompt.

This change does not restore router- or planner-derived language labels as hard policy. request_context.output_language remains the only caller-controlled hard authority. When it is absent, the answering model still owns the language decision, but it makes that decision from the complete user-authored display_message alone. Explicit and implicit cross-language requests remain supported, while short, mixed-language, or context-dependent requests safely fall back to model reasoning without heuristic language detection.

Validation

  • 114 focused context and language seam tests passed
  • 29 existing Auto, DAG, ReAct, and runner language tests passed
  • 9 request-language regression tests passed after rebasing onto the latest upstream/main
  • targeted Ruff lint and format checks passed
  • targeted py_compile checks passed
  • mutation verification passed: temporarily replacing the request-only harness with the previous generic fallback caused the new Toby regression test to fail, and restoring the fix made it pass
@XprobeBot XprobeBot added the bug Something isn't working label Sep 1, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@rogercloud

Copy link
Copy Markdown
Collaborator

ci is failing

@OliverBryant
OliverBryant force-pushed the codex/fix-request-language-consistency branch from e4d2e19 to 476d0fa Compare September 1, 2026 06:45

@rogercloud rogercloud left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR separates user-authored display text from connector-enriched execution context when deriving response language. It introduces a request-only language harness and threads it through root system context, Auto/ReAct guidance, DAG planning, and DAG completion while preserving caller-pinned request_context.output_language as the hard authority. It also adds and updates prompt-level regression assertions for the new seam.

Blocking: yes — recommended event: REQUEST_CHANGES

Approach

Verdict: acceptable-with-reservations. The hard-versus-soft authority split and the shared output_language_directives seam fit the stated problem and cover the relevant response paths. However, the request-only helper currently couples language-rule text to a complete request value; structured plan and completion payloads already carry that value, so this design introduces avoidable unbounded prompt duplication and context-capacity risk. That reservation is a blocking major finding below.

Update summary

The current PR head contains three commits. 01b4efce (fix(agent): isolate response language from connector context) changed production code to add the request-only harness and pass user-authored request text through the root, planner, and completion language paths, with initial coverage. 7af3fad5 (chore(tests): apply isort formatting) only formatted the new harness test, while 476d0fad (test(agent): update language prompt assertions) updated assertions across the Auto, context, DAG, and ReAct tests. The complete history contains no prior technical findings; the only conversation roots are the Gemini quota warning (5488913970) and rogercloud's ci is failing note (5489203088), both informational.

Findings (major)

1. src/xagent/core/agent/context/execution.py:527 — blank display text falls back to connector-enriched execution text

Severity: major
Blocking: yes

  • Reachable trigger: A supported AgentRunner/AgentExecutionAdapter/ExecutionRegistry continuation supplies a non-empty execution_message containing connector or file enrichment and explicitly supplies display_message as "" or whitespace, with no caller-pinned request_context.output_language. The core injection contract rejects a missing value (None) in the relevant split case, but accepts an explicit blank value and persists it in Message.metadata; file-only or attachment-backed turns are supported by this contract.
  • Observable impact: _current_user_request_text(prefer_display=True) only returns display_message when display.strip() is non-empty, then falls back to the full Message.content. This new root call therefore gives request_only_language_harness connector/file text while labeling it as the user-authored request. Foreign-language connector metadata, addresses, quoted source text, or file instructions can steer root/Auto/ReAct prose, DAG plan prose, and DAG completion/final-answer prose into the connector language instead of the user's conversational language, violating the user-visible response-language contract. The same fallback is reached by the new planner calls at src/xagent/core/agent/pattern/dag/plan_generator.py:564,577-583 and completion calls at src/xagent/core/agent/pattern/dag/dag.py:1506,1518-1527.
  • Why the caller/type/API contract permits it: The core APIs expose separate execution_message and display_message values and do not require a non-empty display string; endpoint-specific non-empty text validators do not constrain these core live-control callers. Persistence and checkpoint restoration retain the display metadata, so a blank display value remains a real supported turn rather than being normalized away. A caller-provided output-language pin is a valid mitigation, but the unpinned mode is explicitly supported and is supposed to use user-authored display text as its soft language signal.
  • PR causation: Before this change, the root system-context, planner, and completion language boundaries did not pass these request values into the new request-only harness. The PR's new request= arguments at the root and structured DAG consumers directly expose the existing non-empty-display fallback as the sole quoted language source.
  • Required fix: Make the language-specific extractors distinguish a missing display_message key from a present blank/whitespace value. Preserve any compatibility fallback only for a missing key; for a present blank value, return an empty language request and do not substitute Message.content. Apply that strict semantics to both _current_user_request_text and latest_user_text, and add root, plan, and completion regressions using blank and whitespace display text with foreign connector-enriched execution text. Do not reject blank display text outright, because that would break the supported file-only/attachment turn contract.

2. src/xagent/core/agent/language.py:498 — structured prompts duplicate an unbounded complete request

Severity: major
Blocking: yes

  • Reachable trigger: An unpinned DAG request is large enough to be near the selected provider's finite context window. The supported request contracts permit arbitrary non-empty content (the public schema has no maximum, legacy message inputs are unconstrained strings, and persisted content is Text), while the bespoke planner and completion payloads have no token budgeting or compaction for these added fields.
  • Observable impact: request_only_language_harness now serializes the complete request into output_language_policy. The plan payload already carries latest_user_request and the execution transcript in messages, so this adds approximately one request-sized copy; the completion payload adds the request to output_language_policy and again to user_authored_language_request, on top of its existing transcript fields, adding approximately two copies. The verifier measured a 3,000-character request at roughly 3,422 estimated tokens for the baseline plan payload versus 4,450 now, and roughly 3,098 versus 4,886 for completion using the repository's four-characters-per-token estimator. Thus a provider call that fit a 4,096-token configuration before this PR can be rejected for context length, or lose budget needed for step_results and candidate_output; even below the limit, every request incurs proportional extra input-token and cost work.
  • Why the caller/type/API contract permits it: Complete request text is reachable and cannot simply be truncated: explicit target-language instructions may occur anywhere in the request, and the language contract requires preserving them. No provider-side deduplication contract removes the copies, and context compaction only handles stored ExecutionContext messages, not these structured plan/completion JSON payloads. The structured consumers already have a clean field to reference (latest_user_request in plan_generator.py:576 and user_authored_language_request in dag.py:1524-1526), so carrying the value once is compatible with the existing payload contract.
  • PR causation: In the base code, the plan and completion consumers passed no request to output_language_directives, so their policy text was fixed-size. This PR changed language.py:583-601 to render the full request-only harness for unpinned sections and added the structured request fields/callsites, creating the new request-sized copies.
  • Required fix: Separate rule text from request-value carriage for structured consumers. Keep the complete JSON quote only at the root system-context boundary (or another boundary that genuinely lacks a clean request field); make the plan policy a fixed-size instruction that references latest_user_request, and make the completion policy reference user_authored_language_request. Preserve the clean request once per structured payload and update payload tests to assert field references and the absence of a second full quote. Do not solve this by truncating the request.

Prior-findings checklist

  • No prior technical findings or technical threads require resolution. Review bodies and inline review comments were empty, with no technical replies, duplicate occurrences, or tracking references.
  • [i] Informational history only: comment 5488913970 was a Gemini quota warning, and comment 5489203088 was rogercloud's ci is failing statement. Neither contains a technical finding or reply chain.

Review limitations

The Simplification Lens was unavailable due to the usage limit. No simplification finding is generated, and no empty Simplification opportunities section is included.

Blocking status & recommended decision

Blocking: yes.

Blocking issues:

  • src/xagent/core/agent/context/execution.py:527major — blank display text can make connector/file-enriched execution content control the user-visible response language. [new]
  • src/xagent/core/agent/language.py:498major — unbounded request copies can turn a baseline-fitting DAG call into a provider context-length failure and consume completion payload budget. [new]

Recommended decision: REQUEST_CHANGES

Comment thread src/xagent/core/agent/context/execution.py Outdated
Comment thread src/xagent/core/agent/language.py

@rogercloud rogercloud left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

The runtime keeps two forms of each user turn: the display_message the human actually typed, and an execution message enriched with connector context (sender names, email addresses, localized metadata). The previous soft language policy pointed vaguely at "the current user request," so a question asked in English over, say, a Spanish-language email connector could come back in Spanish. This PR makes request_context.output_language the only hard authority and, when it is absent, feeds the model only the JSON-quoted display_message as language evidence — applied consistently across the root system context, Auto, ReAct, and DAG planning/steps/completion, plus the final-answer schemas.

Round 0 design verdict: acceptable-with-reservations. The fix is the right shape given the constraint: separating display text from connector-enriched execution text is the minimum viable seam, and the deeper root cause — connector context being concatenated into user content at all instead of carried as separate metadata — is a much larger refactor that is reasonably out of scope here. The reservations are about accumulation: the codebase now carries roughly seven overlapping language-prose snippets (output_language_policy, response_language_rules, final_answer_language_rule, dag_step_language_rules, plan_language_rules, request_only_language_harness, _structured_request_language_policy), and that duplication has already produced a real defect (F4 below). The empty-request case also appears never to have been designed for explicitly; a single if not request: return response_language_rules() guard in the two harness builders would have removed several of the findings below at the source.

Update since last review

Since the last round the branch picked up 8c3efb2b ("harden request language boundaries"), 476d0fad ("update language prompt assertions"), 7af3fad5 (isort), and 01b4efce. The substantive change is 8c3efb2b: it introduces the tri-state display_message_override() extractor (distinguishing key-absent from present-but-blank from non-string) and switches the DAG plan and completion payloads to reference-only language policies that name a field instead of re-embedding its value. That addresses both prior findings. The same mechanism, however, introduced several new issues listed under Findings.

Prior findings

Prior Finding 1 — blank display_message fell back to connector-enriched execution content: FIXED. src/xagent/core/agent/context/enrichment.py:113-126 now implements a tri-state contract — missing key / non-dict metadata / non-string value yield None (execution-content fallback preserved only there), while a present string, blank included, is .strip()'d and treated as authoritative-empty with no fallback. Both ExecutionContext._current_user_request_text(prefer_display=True) and latest_user_text(prefer_display=True) route through it. tests/core/agent/test_request_language_harness.py:72-141 (15 tests, run and passing) parametrizes blank and whitespace display_message across the root, DAG-plan, and DAG-completion paths and asserts polluted connector text never reaches the language-governing text. Thread already resolved.

Prior Finding 2 — DAG plan/completion payloads embedded the whole request a second time inside output_language_policy: FIXED. _structured_request_language_policy() at src/xagent/core/agent/language.py:511-526 now references the field name only (e.g. "the latest_user_request field") instead of re-quoting the value. tests/core/agent/test_request_language_harness.py::test_structured_language_payloads_include_a_large_request_exactly_once (run and passing) confirms each structured payload contains the request exactly once. Thread already resolved.

Findings

F1 — MAJOR · Blocking: yes · src/xagent/core/agent/pattern/dag/dag.py:1710-1714

_step_instruction was not updated by this PR: it calls output_language_directives(effective_output_language(root_context), section="dag_step_instruction") with no request=. The dispatcher in language.py has no explicit branch for that section, so it falls through to request_only_language_harness(request) with the default request="". When no output language is pinned — the default case — this renders a harness that JSON-quotes an empty string as the "user-authored request" and tells the model to pick a language from that empty quote. The same DAG step's system prompt already carries a correct harness quoting the real request (dag_step_request_anchor, src/xagent/core/agent/context/execution.py:598-611), and both land in the same LLM call, so the model gets two contradictory language harnesses at once. This is a regression against base, where the same fallthrough emitted output_language_policy(language) — self-contained and non-contradictory even when empty.

Fix: thread request=latest_user_text(root_context, prefer_display=True) through this call site (root_context is already in scope, and the same helper is used at src/xagent/core/agent/pattern/dag/dag.py:1506), or restore output_language_policy(language) as the dag_step_instruction fallback in language.py.

Blocking rationale: every DAG step without a pinned output language emits a self-contradictory language instruction inside a single model call, directly undermining the reliability this PR exists to deliver.

T4 (test, MINOR, tied to F1): tests/core/agent/test_output_language_seam.py:161-163 and tests/core/agent/test_dag.py:5298 currently assert the empty-request harness as the correct DAG-step-instruction output, in setups that explicitly construct a real non-empty user request first. Both codify the defect and must be updated to assert the real request is preserved when F1 is fixed.

F2 — MAJOR · Blocking: yes · src/xagent/core/agent/context/execution.py:521-541

On the root (non-DAG) path with no display_message metadata — the common non-connector case, where display falls back to content — current_task (line 524) and request (line 530, via prefer_display=True) resolve to the same string, and the root system context now appends it twice: once raw under "Current user request:", then again JSON-quoted inside request_only_language_harness(request). Verified empirically: context._system_context().count(request) == 2 for this case. Base used response_language_rules() for the no-pinned-language branch, which embeds no request text at all, so this is a new per-turn token cost proportional to request length on every root LLM call without a pinned output language. The new regression test only covers the deliberately-constructed case where display_message differs from content, so it never exercises this fallback.

Fix: when the harness is appended immediately after the same text was printed verbatim as "Current user request," either drop the quoted copy inside the harness or drop the raw line and rely on the harness alone.

Blocking rationale: an unbounded per-call token regression on the default chat path, untested — for large supported requests it recreates exactly the context-budget risk Prior Finding 2 was fixed for, just in a different code path.

F3 — MAJOR · Blocking: no · src/xagent/core/agent/language.py:529-541 (+ call sites pattern/auto/auto.py:1370,1391, pattern/react/react.py:1086,2075,2091)

The default subject of final_answer_language_rule changed from the self-contained "current user request" to a pure pointer, "authoritative output language guidance in the system context". But the root language block at src/xagent/core/agent/context/execution.py:526 is emitted only when current_task is truthy (if current_task and not dag_step_id:), and current_task can be empty — src/xagent/core/agent/runner.py:227 only calls add_user_message(task, ...) if task:, so an attachment- or context-ref-only turn (and some resumed/scheduled invocations) produces no language guidance block at all, while all five final-answer/decision schemas still tell the model to follow "the authoritative guidance in the system context" — a dangling reference. The base default degraded gracefully in the same scenario. No test exercises the empty-current_task path; test_output_language_seam.py's helpers always add a non-empty user message.

Fix: keep the old self-contained default subject as a base, or fall back to it when the directive block will not render.

Blocking rationale: a real regression with zero coverage, but it requires an edge-case invocation shape (no free-text task) off the mainstream chat path — worth fixing, not worth gating merge.

F4 — MINOR · src/xagent/core/agent/language.py:511-526

subject = f"the \{request_field}` field"is passed intoresponse_language_rules(subject=...), whose template already prepends "the " before {subject}in three places. Rendering the actual output confirms it reads "Use the same natural language as **the the**latest_user_requestfield…", three times, in every DAG plan-generation and completion-assessment prompt._structured_request_language_policy is new in this PR; the helper it misuses is unchanged. Existing tests assert only that the substring "latest_user_request` field" appears, so nothing catches it.

Fix: subject = f"\{request_field}` field"`.

F5 — MINOR · src/xagent/core/agent/pattern/dag/dag.py:1524-1526

When a language is pinned, the payload still emits "user_authored_language_request": "" — a dead key the accompanying policy text never references. Cosmetic clutter, no functional or budget impact; suggest omitting the key entirely when output_language is set.

F6 — LOW, informational · src/xagent/core/agent/runner.py:902-908 (pre-existing) × src/xagent/core/agent/context/enrichment.py:113-126

runner.py unconditionally coerces any present-but-non-string display_message (JSON null included) to "" before display_message_override() ever sees it, so the function's documented "legacy non-string values retain the execution-content fallback" branch is unreachable through the production runner path — it is reachable only from hand-constructed test contexts, which is exactly how test_unsupported_display_metadata_preserves_execution_content_fallback is written. This is practically safe and not a re-opening of Prior Finding 1: the resulting authoritative-empty harness still tells the model to derive language from conversation context, not from polluted execution content. It is a docstring-accuracy and coverage gap. Suggested follow-up: either correct the docstring to match real reachability, or have runner.py preserve None so the documented branch becomes live, plus an end-to-end runner test with display_message: null.

Test quality notes

  • T1tests/core/agent/test_request_language_harness.py:40-59: test_request_language_harness_preserves_the_whole_request_without_detection is parametrized over five inputs of differing language and length, but four of its five assertions check invariant boilerplate constants of the function under test (request_only_language_harness does not branch on input language or length). Only the json.dumps(...) in harness assertion depends on the parametrized value, so all five cases hit identical code with identical branch coverage while implying cross-language discrimination testing that isn't happening.
  • T2tests/core/agent/test_request_language_harness.py:243-251: test_final_answer_schemas_follow_the_shared_language_guidance asserts on literal substrings copied from final_answer_language_rule(). It does verify the guidance is wired into both the ReAct and Auto schemas (it would catch an entirely missing call), but as a string-echo check it would miss the right-looking text landing on the wrong field, or a swapped subject= that still happens to contain those substrings.
  • T3tests/core/agent/test_context.py:743-747: test_dag_step_language_quote_uses_the_typed_message was loosened from quote.startswith(typed) to typed in quote. The loosening is legitimately forced by the new JSON-string wrapping, but a tighter JSON-aware form — quote.startswith(json.dumps(typed, ensure_ascii=False)) — was available and would keep a positional guarantee; today the only remaining safety net is the separate "Attached file(s)" not in quote check.
  • T4 — see under F1.

Simplification opportunities

  • src/xagent/core/agent/language.py L484: shrink — request_only_language_harness (L484) and _structured_request_language_policy (L511) are two ~20-line near-duplicate prose blocks (same "not language evidence" list, same Chinese Simplified/Traditional clause, same "controls language only" clause), differing only in quote-the-value versus name-the-field framing; a single parameterized helper (subject, optional quoted_value, an empty-request wording toggle) covers both call sites — and would have prevented F4, which arose precisely because these two blocks are maintained by hand.

net: -8 to -12 lines possible

Blocking status & recommended decision

Blocking: yes — recommended event: REQUEST_CHANGES

  • [new] F1 — src/xagent/core/agent/pattern/dag/dag.py:1710-1714: missed call-site update emits an empty-request language harness that contradicts the correct harness in the same DAG-step model call, on the default no-pinned-language path.
  • [new] F2 — src/xagent/core/agent/context/execution.py:521-541: the root system context now duplicates the full user request text on every unpinned root call, an untested token-cost regression proportional to request length.

F3-F6 and the test notes are not merge blockers, but F3 and F4 are cheap and worth folding into the same round.

Comment thread src/xagent/core/agent/context/execution.py Outdated
Comment thread src/xagent/core/agent/language.py Outdated
Comment thread src/xagent/core/agent/language.py Outdated
Comment thread src/xagent/core/agent/pattern/dag/dag.py
@OliverBryant

Copy link
Copy Markdown
Contributor Author

Addressed the overall-review items in c785684. DAG step instructions now use fixed-size guidance that points to the authoritative real-request anchor already present in child system context, with no empty JSON quote or duplicated request. Runner behavior remains intentionally unchanged: present null/non-string display values are normalized to authoritative empty text before extraction, and the extractor documentation plus runner-level regressions now make the direct/restored-context fallback boundary explicit.\n\nThe test refactor preserves the explicit Spanish target, Simplified Chinese, Traditional Chinese, short request, mixed-language, Unicode, and special-character cases with diagnostic IDs. Serialization assertions now anchor at the exact JSON field boundary, schema assertions verify exact field placement and complete guidance, and both defective DAG instruction assertions now require the real request in system context but not in step text. Shared setup and soft-policy prose were consolidated without merging the distinct root, structured, empty, step, or pinned behaviors. The 32-test targeted suite, 29 language tests, 13 context-language tests, 8 relevant Auto/ReAct/DAG/runner siblings, isort, Ruff check/format, py_compile, and diff checks pass. Separate F1, F2, and F3 mutations each made its regression fail before restoration. Local pytest-cov support is unavailable in the validated environment, so no local coverage percentage is claimed.

@OliverBryant
OliverBryant force-pushed the codex/fix-request-language-consistency branch from c785684 to 92f9611 Compare September 1, 2026 09:56

@rogercloud rogercloud left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR separates response-language guidance from connector/file-enriched execution text across root, Auto, ReAct, DAG planning/steps/completion, and final-answer schemas, while retaining request_context.output_language as the only caller-controlled hard authority. It introduces shared request-only guidance and carries the display/request boundary through fresh and resumed contexts. The approach is directionally sound, but this re-review confirms three major PR-caused blockers: a blank display loses a DAG child language anchor, a resumed clarification answer becomes completion-language authority, and the root harness adds unbounded request-sized duplication.
Blocking: yes — recommended event: REQUEST_CHANGES

Update summary

Since the previous review, the rebased five-commit sequence is b6402e07 (isolate response language from connector context), c67b55e9 (apply test isort formatting), 37cde4fa (update language-prompt assertions), 47e1257d (harden request-language boundaries), and 92f9611d (close remaining language-prompt review gaps). This review is against the exact range BASE=31a2bb27b1d5174d142b5d8089ef9286eee6e81e through HEAD=92f9611db01499b7327dc9415ae003581233d96f (13 files, +590/-80); the inline anchors below were recomputed for this range.

Round 0 design verdict

Acceptable with reservations. The normalized hard authority, shared renderer, structured-field references, and absence of a persisted heuristic language label fit the stated problem. The design still needed an explicit presentation-versus-authorship boundary, correct classification of resumed DAG clarification answers, and finite-budget ownership for full request text. The synthetic file-only presentation-placeholder concern was independently dropped: those placeholders predate this PR and no deterministic wrong-language regression was established. The resumed-DAG selector and unbudgeted duplication remain confirmed, and Round 1 additionally found the concrete blank-display DAG-child anchor regression below.

Prior-history checklist

All duplicate occurrences and replies are consolidated under one canonical root; no historical root is reopened.

  • FIXED — Blank/whitespace display fallback.
  • FIXED — Structured plan/completion policy re-embedded request.
  • FIXED — DAG-step empty harness / missing step anchor.
  • FIXED — Missing-display root request duplication.
  • FIXED — Dangling default final-answer guidance.
  • FIXED — Doubled the the structured policy.
  • FIXED — Pinned completion dead field.
  • DROPPED — Runner normalization/documentation reachability concern; the relevant code is pre-existing and unchanged in this range.
  • FIXED — Parameterized harness test-invariant redundancy.
  • FIXED — Weak schema test assertions.
  • FIXED — Weakened typed-quote assertion.

Current blocking findings

1. Present blank display suppresses the DAG child language anchor — src/xagent/core/agent/context/execution.py:539 — [major][blocking]

Trigger: An unpinned, supported context has a nonempty execution request containing connector/file enrichment and a present display_message equal to "" or whitespace; a DAG child is then created or restored. Evidence: The tri-state display helper returns a present blank string, and the new branch at execution.py:539 accepts it as authoritative. The child system-context builder passes that value to the DAG request-anchor dispatcher, which still omits the request-only harness when the request is empty. Provider-facing message serialization excludes message metadata, so the child is left with generic current-request guidance while the enriched connector/file text remains visible. Impact: A supported DAG step can use auxiliary connector/attachment text as language evidence and emit user-facing prose or persisted tool arguments in the wrong language, diverging from the root/planner/completion contract. Fix: Preserve the present-empty state with a presence-aware section or equivalent and emit explicit empty-request language guidance for DAG children, without falling back to enriched content; add fresh and restored blank/whitespace child regressions.

2. DAG completion treats a waiting clarification answer as language authority — src/xagent/core/agent/pattern/dag/dag.py:1506 — [major][blocking]

Trigger: An unpinned DAG starts with a substantive request in one language, a child pauses through ask_user/send_message(expect_response=True), and the user supplies substantive clarification in another language without requesting a language change; the run resumes and completes. Evidence: The added selector uses unfiltered latest_user_text(context, prefer_display=True), which reverse-scans user messages without excluding response_to_waiting_for_user or DAG scaffolding. The actual forwarding path marks only the child copy; the root answer seen by completion remains unmarked. When no hard language is pinned, the new user_authored_language_request field therefore contains the clarification answer, and its field-only policy makes that value the sole soft language authority; completion strips metadata and has no downstream language validator to mitigate it. Impact: Final DAG synthesis can switch to the clarification answer's language even though the original request established the language and the answer did not request translation. Fix: Preserve pending-question classification on forwarded root answers (including checkpoint-restored resumes) and use the marker-aware top-level request selector for completion and replanning; add live and restored cross-language wait/resume regressions.

3. Root request-only harness duplicates the full request without a finite budget — src/xagent/core/agent/language.py:496 — [major][blocking]

Trigger: Send a nonempty request through a supported WebSocket or Slack file/connector turn with no pinned output language, so the display request is R while the execution request is R+F. Evidence: The added json.dumps(request, ensure_ascii=False) always serializes the complete request with no bound or deduplication. The root system context already renders the enriched execution request, and the user message carries that same enriched request; the new harness adds another full R. Normal compaction truncates visible messages only, does not budget generated system context, and ordinary Auto/ReAct calls do not supply a max-token limit. Impact: A finite request can fit the BASE prompt but exceed the provider context in HEAD, causing a context-length failure before the answer or forcing loss of required context. Fix: Keep language isolation while carrying one canonical full request/reference and account for it in the actual prompt budget, separating enrichment where necessary; do not silently truncate the request because language-bearing instructions may occur mid-request.

Limitations

No local tests, linters, formatters, dependency installs, or runtime validation were run. All 15 reported CI checks completed successfully at the final preflight. The history extractor and simplification scans were unavailable (usage_limit_reached); prior history was manually reconstructed from the complete review, conversation, and inline-comment exports. No linked issues were supplied.

Blocking status & recommended decision

Blocking: yes — recommended event: REQUEST_CHANGES

Blocking issues:

  • src/xagent/core/agent/context/execution.py:539, major — present blank display suppresses the DAG child request-only language anchor and permits enriched auxiliary text to bias user-visible language. [new]
  • src/xagent/core/agent/pattern/dag/dag.py:1506, major — an unfiltered resumed clarification answer can become the sole completion-language request and switch final synthesis language. [new]
  • src/xagent/core/agent/language.py:496, major — an unbounded duplicate request can push a previously fitting enriched turn over the finite provider context limit. [new]

Each listed root has a supported trigger, concrete impact, and current-code evidence of PR causation against the exact BASE/HEAD range. The fixed and dropped historical roots do not contribute to the blocking decision.

display = str(message.metadata.get("display_message") or "").strip()
if display:
display = display_message_override(metadata)
if display is not None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major][blocking] A present blank display_message is now accepted here, but the DAG child still passes that empty value to its request-anchor dispatcher, whose not request path emits no request-only language guidance. With an unpinned request whose execution content contains connector/file enrichment, fresh and restored children then retain only generic current-request guidance while provider messages hide display metadata, so auxiliary text can determine the language. Preserve the present-empty state and emit an explicit empty request-only anchor without falling back to enriched content; add fresh/restored DAG regressions.

return assessment

def _completion_assessment_messages(self, context: Any) -> list[dict[str, Any]]:
language_request = latest_user_text(context, prefer_display=True) or ""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major][blocking] This new selector scans the latest display-preferred user message without excluding response_to_waiting_for_user. In a supported unpinned DAG wait/resume, a cross-language clarification answer becomes user_authored_language_request, and the field-only completion policy can make final prose follow that answer rather than the original request. Mark forwarded root answers on live and restored paths and use the marker-aware root selector so completion retains the original language request.

return (
"Request-only response language harness:\n"
"User-authored request (JSON string):\n"
f"{json.dumps(request, ensure_ascii=False)}\n\n"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major][blocking] json.dumps(request, ensure_ascii=False) unconditionally serializes the full request into the harness. For supported unpinned file/connector turns, execution content already contains that request plus enrichment and is sent in both system context and user messages; normal compaction does not budget generated system context. A request that fit BASE can therefore exceed the finite provider context in HEAD and fail before answering. Carry one canonical request/reference and account for it in the prompt budget without truncating language-bearing input.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

3 participants