Skip to content

feat(subagents): preserve RAG evidence in durable batch exports - #6413

Merged
WillemJiang merged 18 commits into
bytedance:mainfrom
Guo-Yixin:feat/batch-rag-evidence
Oct 7, 2026
Merged

WillemJiang merged 18 commits into
bytedance:mainfrom
Guo-Yixin:feat/batch-rag-evidence

Conversation

@Guo-Yixin

@Guo-Yixin Guo-Yixin commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Closes #6405

Scope proposal: discussion #6406.

Why

A durable knowledge-research item can return a report containing #knowledge-<id> citations, but the batch worker currently stores only its report text. After a Gateway restart, the owner can export the report without the exact retrieved excerpts those IDs referred to. Refetching the provider would not reconstruct the evidence used by that run.

This extends the existing captured-source protocol from #5551 to the native durable batch result/export chain. It follows the existing subagent result work rather than adding a retrieval provider or changing batch scheduling. The gap is reproduced at the current base through the production RAGFlow formatter, captured ToolMessages, the durable worker, reopened SQLite and the existing JSONL consumer.

What changed

Successful durable research results now retain a bounded result_artifact.knowledge_sources snapshot alongside the report. A caller can resolve a retained report citation to the original whole excerpt, source ID, dataset/document/chunk locator and pages using the existing owner/thread-authorized results.jsonl export, even after the worker has stopped or provider content has changed.

  • Select only captured RAGFlow sources referenced by complete citations in the stored, capped report, including sources forwarded by a delegated task. Store only the documented source fields, not child messages or arbitrary artifacts.

  • Keep the existing source-count/text forwarding limits. Bound the serialized snapshot separately by subagent_batches.max_result_chars, in addition to the report limit. Skip excerpts that do not fit intact and report omissions among accepted candidates; malformed/unsupported producer artifacts rejected before selection are not counted.

  • Persist the snapshot in the existing lease-fenced successful-result transaction. Failed, cancelled and superseded attempts do not publish it; explicit retries clear previous snapshots. Execution, retry and acceptance policies are unchanged.

  • Add nullable migration 0033_batch_result_artifact after the shipped 0032_activity_and_task_events head. Legacy/plain results export result_artifact: null; there is no evidence backfill or provider refetch. Gateway startup applies the migration. Take a database backup before upgrading; downgrading drops evidence snapshots and preserves report text.

This is an API/export capability. Compact item/progress responses and batch-panel previews do not include the snapshot; clickable citations in those previews are outside this PR. The native worker and its atomic result persistence own this boundary, which the current extension hooks cannot replace atomically.

Reproducible entry

Offline acceptance (no model or RAGFlow service required):

cd backend



uv sync --locked



PYTHONPATH=. uv run --locked pytest tests/test_batch_rag_evidence.py -k 'survives_worker or real_subagent or http_export' -q


The tests exercise direct/delegated evidence, a production subagent graph with a deterministic fake model, SQL teardown/reopen and the actual JSONL route. In a configured deployment, run an existing batch_task knowledge-research item and, as its owner, fetch:

GET /api/threads/{thread_id}/subagent-batches/{batch_id}/results.jsonl


For each exported row, match a #knowledge-<id> report destination to result_artifact.knowledge_sources.sources[].id. The matching record's text is the captured excerpt; omitted_count makes budget/projection omissions visible. These deployment instructions describe the existing authenticated entry; no live model/provider experiment was performed for this PR.

Surface area

  • Frontend UI

  • Backend API — additive field in the existing full-result JSONL response

  • Agents / LangGraph — captured tool evidence flows into durable worker results

    • Prompt-layer self-check: retrieval content remains untrusted structured data. This PR adds no framework-owned system text, instruction interpolation or execution authority.
  • Sandbox

  • Skills

  • Dependencies — no dependency changes in this PR's base-to-head diff

  • Default behavior change — successful knowledge results retain bounded evidence; full exports gain a nullable field

  • Docs / tests / CI only

Validation — published head be3ae119

Addressed the P2 migration-fixture TLS review. The 0033 migration fixture now uses the same support.postgres.asyncpg_test_url as the batch evidence fixture, mapping sslmode to ssl and rejecting conflicting TLS options. It no longer discards require/verify-ca/verify-full. Production connection policy, migration DDL and runtime evidence behavior are unchanged by this review patch.

Eight new connection-contract regressions execute the actual migration test setup and inspect real SQLAlchemy dialect arguments, stopping before database acquisition. They cover six explicit TLS modes plus an unspecified-mode control and conflict rejection before engine creation. The same probes fail on the old setup: 7 failed / 1 passed; after the fix: 8 passed. This validates option propagation, not a live certificate-handshake experiment.

Before editing, integrated official main through db40811ac45146b3317c8a3af6c60c91aa464ae7 with clean merges, preserving the upstream paused-batch retry fix alongside this PR's evidence handling. The synchronized pre-patch PR head 58a29a988365a754d194d78c568a04b7ab451f75 completed the required before suites; it is a PR comparison point, not unmodified official main. Full and database results below bind to review-fix commit 77e7bfc7bbc317486288db7fa89fef76940b69de.

Before publication, also merged official main f0476677de2f5011c86847e00adb41a08862c046 cleanly, producing be3ae11940c9d9c30dc0c2a9de311dfd9b01a9af. That upstream increment changes only frontend files and changelogs: the entire backend Git tree is identical at both final revisions (96dd025ed5a76b0bf0765aa28a65baa0c65ad722). On the published merge head, reran the migration/helper regressions (33 passed, 1 database case skipped), blocking-I/O (228 passed), backend lint/format, lock, diff and guidance checks. The complete four-shard and isolated PostgreSQL suites were run at 77e7bfc7; they were not rerun after this backend-identical frontend-only merge. This PR has no own frontend diff.

| Check | Synchronized pre-patch 58a29a98 | Final 77e7bfc7 |

|---|---|---|

| Full backend, all four shards, sequential TEST_JOBS=2 | 25,784 passed, 385 skipped, 0 failed | 25,792 passed, 385 skipped, 0 failed |

| Blocking-I/O | 228 passed | 228 passed |

| Focused regression, default locked extras | — | 227 passed, 17 skipped |

| PostgreSQL acceptance, locked postgres extra, server 18.6, ?sslmode=disable | — | 85 passed, 0 failed |

| Migration setup TLS regressions | 7 failed, 1 passed | 8 passed |

PostgreSQL acceptance includes the actual upgrade/downgrade/re-upgrade and RAG worker/reopen/export cases plus SQLite and offline driver controls; not all 85 tests are PostgreSQL connections. Full suites use default locked extras with no database URI; optional cases skipped there are exercised separately in the real database suite. Only disposable UUID schemas in an isolated local cluster were used; public-table count remained zero, all owned schemas were dropped and the temporary server stopped. The user's database was not started or accessed.

Ruff format/lint, dependency lock, diff and agent-guidance checks passed (guidance soft warnings only). Independent AI review of this patch found no remaining actionable issues. No paid model, live RAGFlow or browser/provider experiment was performed.

The first final full-suite attempt had two 5-second worker-wait timeouts in unchanged test_extension_agent_runs.py cases. Both cases passed at the pre-patch and final revisions in targeted controls; executing the migration tests before and after those cases also passed. The precise cause of the initial timeouts was not established, and they are not claimed as an upstream baseline failure. A subsequent diagnostic rerun omitted the explicit proxy/NO_PROXY environment and produced loopback HTTP errors; it was stopped and is not counted as passing. The final table uses a new complete four-shard rerun with the original explicit environment. Initial failures and the incomplete diagnostic run remain recorded separately; no waits/assertions were relaxed and no cases were skipped to obtain the final result.

Earlier remote CI evidence

The previous PR head c77f8555 was subsequently approved to run and its Unit Tests attempt 2 succeeded on PostgreSQL 17: all four shards and default-install collection passed. At verification, 14 checks succeeded and 2 conditional Triage checks were skipped, with no failure or pending job. That result belongs to c77f8555, not the new review patch. The default CI URI had not exercised the TLS modes identified by the reviewer. New-head CI and review status are checked separately after publication.

Current published-head remote CI

Verified at 2026-10-07T10:33:42.945717+00:00 for PR head be3ae11940c9d9c30dc0c2a9de311dfd9b01a9af: all core workflows and CLA succeeded; 15 checks succeeded and 4 conditional Triage checks were skipped, with no pending or failed check. Unit Tests run 37605430684, attempt 2 completed all four backend shards and default-install collection: 26,073 passed / 0 failed / 104 skipped across the shards. Actual GitHub checkout was PR merge commit 36f895b57b4da6596bf23f8a921a877a51d86482 (head be3ae119 into base f0476677), verified in checkout logs for collection and all four shards. This is actual GitHub validation of the published PR head, separate from the local results and earlier c77f8555 run. Maintainer merge approval remains a separate review decision.

| Own diff category | Added | Deleted |

|---|---:|---:|

| Production code | 92 | 3 |

| Tests | 616 | 4 |

| Documentation | 46 | 4 |

| Total (18 files) | 754 | 11 |

AI assistance

Tool(s) used: OpenAI Codex.

How you used it: Codex reproduced the issues, implemented the fixes and regression tests, ran validation, and prepared this description. A separate AI reviewer examined the changes. Human review responsibility remains with the contributor.

  • I've read and understand every line of this change and take responsibility for it — it's not unreviewed AI output.

@github-actions github-actions Bot added area:agents Agents, subagents, graph wiring, prompts, langgraph.json area:backend Gateway / runtime / core backend under backend/ area:docs Documentation and Markdown only needs-validation Touches front/back contract surface; needs real-path validation risk:high High risk: backend API, agents, sandbox, auth, deps, CI size/L PR changes 300-700 lines labels Oct 7, 2026
@github-actions github-actions Bot added size/XL PR changes 700+ lines and removed size/L PR changes 300-700 lines labels Oct 7, 2026
@Guo-Yixin

Copy link
Copy Markdown
Contributor Author

Fixed the PostgreSQL CI fixture errors in c77f855, after merging current official main (00325790). The approved previous run's 16 errors came from passing libpq sslmode=disable directly to asyncpg. Earlier local PostgreSQL acceptance used a URI without that query, so it missed this mismatch.

The fixture now adapts the test URI before setup, reopen and cleanup: sslmode becomes ssl, unsupported channel_binding is removed, and TLS modes/credentials/other options are preserved. Conflicting TLS values are rejected. Production connection policy and the batch evidence behavior are unchanged; no CI assertions were relaxed or PostgreSQL cases skipped.

Validation at the final commit: 25,639 passed / 0 failed in the four-shard backend suite (TEST_JOBS=2, sequential); 228 blocking-I/O passed; 77 acceptance tests passed against a disposable local PostgreSQL 18.6 server using the CI-shaped URI (including SQLite and driver-contract controls). The same pre-fix fixture reproduced all 16 errors; 24 driver-contract regressions and independent AI review also passed. Updated the PR description with the exact SHA, profiles and results.

The new Unit Tests run is currently action_required, awaiting maintainer approval to execute on GitHub's PostgreSQL 17 service. Please approve the new workflow run when available; this is a pending approval, not a successful remote test result.

pytest.skip("requires TEST_POSTGRES_URI (real Postgres batch evidence migration)")
parts = urlsplit(uri)
scheme = "postgresql+asyncpg" if parts.scheme in {"postgres", "postgresql"} else parts.scheme
query = urlencode([(key, value) for key, value in parse_qsl(parts.query, keep_blank_values=True) if key not in {"sslmode", "channel_binding"}])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Preserve TLS policy in the migration fixture

When TEST_POSTGRES_URI contains sslmode=verify-full (or require/verify-ca), this filter discards the requested TLS policy instead of translating it to asyncpg's ssl option. The migration fixture then falls back to asyncpg's default prefer mode, so verify-full no longer enforces certificate and hostname verification. It also silently accepts conflicting ssl/sslmode options.

Please use support.postgres.asyncpg_test_url(uri) here, as the batch evidence fixture already does. I reproduced the discarded modes and accepted conflict by executing this fixture's setup through SQLAlchemy's dialect argument conversion without connecting to a database.

Reference: https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.connect

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 77e7bfc (published head be3ae11 after clean upstream merges). The 0033 migration fixture now reuses support.postgres.asyncpg_test_url(uri), preserving the requested TLS mode and rejecting conflicting ssl/sslmode values before engine creation.

Added eight regressions that execute the actual migration test setup through SQLAlchemy's dialect argument conversion and stop before database acquisition. Old setup: 7 failed / 1 passed; fixed setup: 8 passed. They cover all six explicit TLS modes, the unspecified-mode control, and conflicting options. These prove argument propagation, not a live TLS certificate handshake.

At 77e7bfc: all four backend shards with TEST_JOBS=2 completed with 25,792 passed / 0 failed / 385 skipped; blocking-I/O 228 passed; isolated PostgreSQL 18.6 acceptance and driver/SQLite controls 85 passed, including upgrade/downgrade/re-upgrade. At be3ae11: migration/helper regressions 33 passed / 1 optional database skip, blocking-I/O 228 passed, lint/format/lock/diff/guidance checks passed. The two revisions have identical entire backend Git trees; only upstream frontend/changelog updates were merged. Independent review found no remaining actionable issue. Detailed per-SHA evidence and unsuccessful initial attempts are retained in the PR description.

Production connection policy and migration DDL are unchanged by this review patch. Please re-review; if the new-head workflows require approval, please approve them so their actual CI results can be checked.

@WillemJiang WillemJiang added this to the 2.2.0 milestone Oct 7, 2026
@WillemJiang
WillemJiang merged commit 3182ecb into bytedance:main Oct 7, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agents Agents, subagents, graph wiring, prompts, langgraph.json area:backend Gateway / runtime / core backend under backend/ area:docs Documentation and Markdown only needs-validation Touches front/back contract surface; needs real-path validation risk:high High risk: backend API, agents, sandbox, auth, deps, CI size/XL PR changes 700+ lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] Preserve bounded RAG evidence in durable batch result exports

3 participants