Skip to content

fix(deep-research): make quantitative artifacts reliable - #373

Closed
KyleZheng1284 wants to merge 6 commits into
NVIDIA-AI-Blueprints:release/2.2from
KyleZheng1284:fix/deep-research-convergence
Closed

KyleZheng1284 wants to merge 6 commits into
NVIDIA-AI-Blueprints:release/2.2from
KyleZheng1284:fix/deep-research-convergence

Conversation

@KyleZheng1284

@KyleZheng1284 KyleZheng1284 commented Jul 21, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Make quantitative deep-research output converge and publish durably across sandbox providers. OpenShell exposed the loop, but the failure is provider-neutral: quantitative evidence needs a validated handoff into writer-owned publication, operators need optional convergence controls, and async jobs need a configurable terminal deadline.

What actually changes

The core fix is one explicit ownership handoff:

Stage Before After
Researcher Produced loosely structured table/CSV text and could see chart-publication instructions Performs analysis once and returns a validated QuantitativeDataset in ResearchNotes
Runtime handoff No durable identity connected the researched data to the final artifact Validates the CSV, computes its SHA-256 digest, and registers dataset ID + path + digest
Writer Received both research and synthesis skill collections and could redo analysis during publication Receives only synthesis, writes the report baseline, publishes the exact validated CSV, and optionally charts that CSV
Artifact capture Could scan and publish files without preserving canonical identity Verifies the registered identity and digest during checkpoints and final scanning

The visible skill-directory change supports that ownership boundary; it is not the whole fix:

research/data-table-analysis       -> researcher analyzes and returns QuantitativeDataset
synthesis/chart-generation         -> writer publishes exact CSV and optional PNG

Skill routing is collection-based. Moving chart-generation from research to synthesis is therefore the smallest way to keep analysis researcher-owned and publication writer-owned. Leaving it under research would require giving the writer the entire research collection again; adding per-skill routing would introduce a larger configuration/schema change.

This branch is rebased onto the current release/2.2 head 380a801 and preserves the release fixes already merged through:

Supporting hardening

  • Canonical capture rejects missing, unregistered, and mismatched identities with stable reasons. Registered canonical paths cannot reappear as unverified legacy artifacts through final scanning.
  • A published canonical digest without a matching validated ResearchNotes dataset now invalidates both writer-report extraction and deterministic fallback.
  • Manifest absence requires the structured file_not_found reason; arbitrary backend text containing not found or 404 remains fail-closed and cannot enable directory scanning.
  • Generic writer-owned PDFs, notebooks, JSON, and unrelated images remain supported without quantitative CSV/table requirements.
  • CRLF/LF differences are normalized consistently in canonical table checks.
  • A deterministic validated ResearchNotes fallback is used only when the writer did not commit an acceptable current-run report.
  • Timeout/cancellation terminal-state races and artifact cleanup are bounded and idempotent.
  • Per-researcher execution budgets, writer-chart execution budgets, and the async workflow watchdog are available but disabled by default. Explicit researcher budgets are forwarded consistently through direct and async construction. The shipped Modal and OpenShell profiles do not silently enable them.

This PR intentionally does not add a deterministic publisher tool, a file-backed large-dataset plane, row-level lineage, per-skill routing, a chart-skill rename, or a new partial-success API status. The implementation is split into four signed commits: release rebase/integration, focused convergence-gap fixes, the compatibility adjustment that makes convergence safeguards opt-in, and focused review hardening.

DCO sign-off for the squash commit

Signed-off-by: Kyle Zheng kyzheng@nvidia.com
Signed-off-by: Kyle Zheng 126034466+KyleZheng1284@users.noreply.github.com

Validation

  • .venv/bin/ruff check --no-cache . — passed
  • .venv/bin/ruff format --check --no-cache . — 375 files already formatted
  • .venv/bin/python -m pytest -q — 2,011 passed, 13 skipped, 23 dependency warnings
  • Focused deep-research and async-runner suite — 658 passed, 6 skipped
  • .venv/bin/nat validate --config_file configs/config_domain_routing_and_skills.yml — passed
  • .venv/bin/nat validate --config_file configs/config_openshell.yml — passed
  • .venv/bin/sphinx-build -W --keep-going -b html docs/source docs/build/html — passed before the review-only skill documentation addition; no Sphinx source changed afterward
  • .venv/bin/pre-commit run --all-files — all hooks passed, including root/MCP lock checks, secret detection, skill validation, notebook output clearing, and the network-backed Markdown link check
  • Live Modal quantitative report + CSV + PNG artifact API E2E remains a pre-merge manual gate.
  • Live OpenShell 0.0.88 quantitative report + CSV + PNG artifact API E2E, including terminal sandbox absence, remains a pre-merge manual gate.
  • I ran the relevant local checks or explained why they are not applicable.
  • I added or updated tests for behavior changes.
  • I updated documentation for user-facing or contributor-facing changes.
  • I confirmed this PR does not include secrets, credentials, or internal-only data.
  • I certify this contribution under the Developer Certificate of Origin (DCO) and signed my commits with git commit -s or an equivalent sign-off.
  • I replaced the DCO sign-off placeholder with my GitHub commit identity and kept the required angle brackets around the email address.

Where should reviewers start?

Review the ownership handoff in this order:

  1. skills/research/data-table-analysis/SKILL.md and models/subagent_contracts.py — researcher output contract.
  2. factory.py and the two shipped configs — collection routing from researcher to writer.
  3. skills/synthesis/chart-generation/SKILL.md and custom_middleware.py — writer-owned publication without re-analysis.
  4. sandbox/artifacts/manager.py — digest-protected capture, structured manifest absence, and scan behavior.
  5. agent.py and frontends/aiq_api/src/aiq_api/jobs/runner.py — report fail-closed behavior, deterministic fallback, optional execution budgets, and terminal hardening.

The provider-neutral acceptance path is in test_artifact_publication_regression.py; default compatibility and explicit-budget behavior are covered in test_agent.py, test_factory.py, test_custom_middleware.py, test_chart_artifact_contract.py, and test_runner.py; the artifact list/content API round-trip is in test_deep_research.py.

Related Issues

Summary by CodeRabbit

  • New Features
    • Added opt-in deep-research convergence safeguards: per-researcher and per-writer execution limits, plus a workflow timeout.
    • Introduced canonical quantitative datasets with SHA-256 verification based on exact UTF-8 CSV bytes.
    • Updated durable artifact publishing to be writer-only, with revised checkpoint timing and deterministic success/failure finalization.
  • Bug Fixes
    • Tightened sandbox isolation contracts, cancellation/cleanup bounds, and fail-closed manifest handling with stable rejection diagnostics.
  • Documentation
    • Updated README and deep-research/sandbox/deployment guidance to match the provider-neutral sandbox and durable-artifacts policies.

@KyleZheng1284
KyleZheng1284 requested a review from a team July 21, 2026 00:35
@coderabbitai

coderabbitai Bot commented Jul 21, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Deep research adds canonical quantitative dataset validation, writer-only durable artifact publication, bounded chart retries, deterministic report fallback, workflow deadlines, and bounded sandbox teardown. Configurations, prompts, documentation, and regression tests reflect these runtime contracts.

Changes

Deep Research Runtime

Layer / File(s) Summary
Canonical data and publication contracts
src/aiq_agent/agents/deep_researcher/models/*, src/aiq_agent/agents/deep_researcher/prompts/*, src/aiq_agent/agents/deep_researcher/skills/*
Quantitative datasets preserve exact CSV bytes and computed SHA-256 digests; prompts and skills assign durable publication to the writer.
Artifact harvesting and execution controls
src/aiq_agent/agents/deep_researcher/sandbox/artifacts/*, src/aiq_agent/agents/deep_researcher/custom_middleware.py, src/aiq_agent/agents/deep_researcher/factory.py
Manifest harvesting validates trusted canonical digests, reports bounded rejections, guards artifact-directory ownership, and limits writer chart execution.
Research fallback and persistence
src/aiq_agent/agents/deep_researcher/agent.py, src/aiq_agent/agents/deep_researcher/tools/research.py
Invalid quantitative output can be salvaged into bounded textual notes, while persisted notes register canonical digests only after successful persistence.
Workflow timeout and teardown
frontends/aiq_api/src/aiq_api/jobs/runner.py, src/aiq_agent/agents/deep_researcher/register.py
Optional workflow deadlines record stable timeout failures and run cancellation, harvesting, termination, and provider release under bounded waits.
Configuration, documentation, and tests
configs/*, docs/source/*, tests/aiq_agent/*, tests/aiq_agent/jobs/*
Sandbox routing, artifact lifecycle, timeout settings, API delivery, canonical bytes, chart budgets, and regression behavior are documented and tested.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title uses Conventional Commits and accurately summarizes the quantitative artifact reliability work.
Description check ✅ Passed The description follows the required template with overview, DCO sign-off, validation, reviewers, and related issues.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@KyleZheng1284
KyleZheng1284 marked this pull request as draft July 21, 2026 00:41
@copy-pr-bot

copy-pr-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aiq_agent/agents/deep_researcher/custom_middleware.py`:
- Around line 1123-1137: Update _contains_all_published_canonical_tables to
normalize both baseline and each markdown_table by converting \r\n and \r to \n
before substring matching, matching the normalization used by
_baseline_contains_tables. Preserve the existing published-digest coverage
checks and compare only the normalized values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: dba1af55-9a62-4189-b5bb-2df7e3828da0

📥 Commits

Reviewing files that changed from the base of the PR and between e9025bb and 2f51e04.

📒 Files selected for processing (40)
  • CHANGELOG.md
  • README.md
  • configs/config_domain_routing_and_skills.yml
  • configs/config_openshell.yml
  • docs/source/architecture/agents/deep-researcher.md
  • docs/source/architecture/agents/sandbox.md
  • docs/source/architecture/data-flow.md
  • docs/source/customization/configuration-reference.md
  • docs/source/deployment/openshell.md
  • docs/source/examples/skills-sandbox/index.md
  • docs/source/integration/rest-api.md
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
  • src/aiq_agent/agents/deep_researcher/factory.py
  • src/aiq_agent/agents/deep_researcher/models/__init__.py
  • src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py
  • src/aiq_agent/agents/deep_researcher/prompts/orchestrator.j2
  • src/aiq_agent/agents/deep_researcher/prompts/researcher.j2
  • src/aiq_agent/agents/deep_researcher/prompts/writer.j2
  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/sandbox/README.md
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py
  • src/aiq_agent/agents/deep_researcher/skills/research/chart-generation/SKILL.md
  • src/aiq_agent/agents/deep_researcher/skills/research/data-table-analysis/SKILL.md
  • src/aiq_agent/agents/deep_researcher/skills/synthesis/chart-generation/SKILL.md
  • src/aiq_agent/agents/deep_researcher/tools/research.py
  • tests/aiq_agent/agents/deep_researcher/models/test_subagent_contracts.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py
  • tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py
  • tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/agents/deep_researcher/test_factory.py
  • tests/aiq_agent/agents/deep_researcher/tools/test_research.py
  • tests/aiq_agent/fastapi_extensions/test_deep_research.py
  • tests/aiq_agent/jobs/test_runner.py
💤 Files with no reviewable changes (1)
  • src/aiq_agent/agents/deep_researcher/skills/research/chart-generation/SKILL.md
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: Script Validation
  • GitHub Check: Pytest and Coverage
  • GitHub Check: Lint and Hooks
🧰 Additional context used
📓 Path-based instructions (8)
src/aiq_agent/agents/**/*

⚙️ CodeRabbit configuration file

src/aiq_agent/agents/**/*: Review agent changes for research workflow correctness, graph state transitions, prompt/tool contracts,
HITL behavior, and failure handling. Flag changes that weaken source attribution, report generation,
async cancellation, checkpointing, or data-source selection without focused tests and docs.

Files:

  • src/aiq_agent/agents/deep_researcher/prompts/orchestrator.j2
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py
  • src/aiq_agent/agents/deep_researcher/models/__init__.py
  • src/aiq_agent/agents/deep_researcher/skills/synthesis/chart-generation/SKILL.md
  • src/aiq_agent/agents/deep_researcher/prompts/writer.j2
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py
  • src/aiq_agent/agents/deep_researcher/sandbox/README.md
  • src/aiq_agent/agents/deep_researcher/prompts/researcher.j2
  • src/aiq_agent/agents/deep_researcher/skills/research/data-table-analysis/SKILL.md
  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py
  • src/aiq_agent/agents/deep_researcher/tools/research.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py
  • src/aiq_agent/agents/deep_researcher/factory.py
docs/source/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Update the docs under docs/source/ when behavior, configuration, or workflows change

Files:

  • docs/source/architecture/agents/sandbox.md
  • docs/source/customization/configuration-reference.md
  • docs/source/deployment/openshell.md
  • docs/source/integration/rest-api.md
  • docs/source/architecture/data-flow.md
  • docs/source/architecture/agents/deep-researcher.md
  • docs/source/examples/skills-sandbox/index.md
{docs/**,README.md,CONTRIBUTING.md,SECURITY.md,CODE-OF-CONDUCT.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,CONTRIBUTING.md,SECURITY.md,CODE-OF-CONDUCT.md}: Review documentation for command accuracy, branch-name consistency, current CI and copy-pr-bot behavior, public
vs internal boundary clarity, stale examples, and links that no longer match the repository layout.

Files:

  • docs/source/architecture/agents/sandbox.md
  • docs/source/customization/configuration-reference.md
  • docs/source/deployment/openshell.md
  • docs/source/integration/rest-api.md
  • docs/source/architecture/data-flow.md
  • docs/source/architecture/agents/deep-researcher.md
  • README.md
  • docs/source/examples/skills-sandbox/index.md
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run ruff check and ruff format validation for Python code changes

**/*.py: Python code must be linted and formatted with Ruff using line length 120, target Python 3.11, rule sets E,F,W,I,PL,UP, and isort force-single-line configuration
Never commit secrets, tokens, or environment-specific hostnames; use environment variables and SecretStr instead, resolving API keys at runtime
Never print or log secret values, including in tool output or error messages
Missing-secret paths must degrade gracefully (stub/skip), not crash or leak
Do not hand-reformat unrelated code when making changes; match the existing import and formatting style

Files:

  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py
  • src/aiq_agent/agents/deep_researcher/models/__init__.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py
  • tests/aiq_agent/fastapi_extensions/test_deep_research.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/register.py
  • tests/aiq_agent/agents/deep_researcher/models/test_subagent_contracts.py
  • tests/aiq_agent/agents/deep_researcher/test_factory.py
  • src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py
  • src/aiq_agent/agents/deep_researcher/tools/research.py
  • tests/aiq_agent/agents/deep_researcher/tools/test_research.py
  • tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py
  • tests/aiq_agent/jobs/test_runner.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py
  • tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py
  • src/aiq_agent/agents/deep_researcher/factory.py
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
src/aiq_agent/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/aiq_agent/**/*.py: Respect authenticated data sources by honoring requires_auth, per-user token pass-through, and backend token validators; apply owner guardrails before loading protected report or artifact context into an agent
Do not weaken or bypass AuthMiddleware, validators, or auth gating without a prior design discussion

Files:

  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py
  • src/aiq_agent/agents/deep_researcher/models/__init__.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py
  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py
  • src/aiq_agent/agents/deep_researcher/tools/research.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py
  • src/aiq_agent/agents/deep_researcher/factory.py
{deploy/**,configs/**}

⚙️ CodeRabbit configuration file

{deploy/**,configs/**}: Review deployment and config changes for secret separation, safe defaults, local-vs-production behavior, Helm and
Docker portability, and documentation parity. Flag committed credentials, environment-specific NVIDIA internals in
public defaults, and changes that make examples diverge from CI-tested paths.

Files:

  • configs/config_openshell.yml
  • configs/config_domain_routing_and_skills.yml
**/*test*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run pytest for all behavior changes in Python code

Files:

  • tests/aiq_agent/fastapi_extensions/test_deep_research.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/agents/deep_researcher/models/test_subagent_contracts.py
  • tests/aiq_agent/agents/deep_researcher/test_factory.py
  • tests/aiq_agent/agents/deep_researcher/tools/test_research.py
  • tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py
  • tests/aiq_agent/jobs/test_runner.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py
  • tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py
{src/aiq_agent/fastapi_extensions/**,frontends/aiq_api/src/aiq_api/**}

⚙️ CodeRabbit configuration file

{src/aiq_agent/fastapi_extensions/**,frontends/aiq_api/src/aiq_api/**}: Treat API, auth, and job-runner changes as externally visible contracts. Check authorization boundaries,
request tracing, async job lifecycle, websocket reconnect behavior, error responses, and cross-user data isolation.
Require tests for route behavior, access decisions, and job state transitions when those surfaces change.

Files:

  • frontends/aiq_api/src/aiq_api/jobs/runner.py
🧠 Learnings (1)
📚 Learning: 2026-07-06T23:55:42.908Z
Learnt from: cdgamarose-nv
Repo: NVIDIA-AI-Blueprints/aiq PR: 311
File: src/aiq_agent/agents/deep_researcher/prompts/orchestrator.j2:74-81
Timestamp: 2026-07-06T23:55:42.908Z
Learning: In agent test files (e.g., tests/aiq_agent/agents/*/test_agent.py), avoid brittle assertions that match exact substrings from prompt template files (such as *.j2 prompt wording). Prompt wording can change frequently, so instead assert structural/behavioral properties (e.g., that the prompt builder is called, that required sections/fields are present via stable markers, that the model output/agent behavior conforms to an expected schema, or that key actions are taken) rather than matching literal prompt text.

Applied to files:

  • tests/aiq_agent/agents/deep_researcher/test_agent.py
🪛 ast-grep (0.44.1)
tests/aiq_agent/fastapi_extensions/test_deep_research.py

[info] 296-308: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{
"path": csv_path,
"kind": "dataset",
"inline": False,
"expected_sha256": digest,
}
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[warning] 368-368: Do not make http calls without encryption
Context: "http://test"
Note: [CWE-319] Cleartext Transmission of Sensitive Information.

(requests-http)


[warning] 368-368: Configuring an LLM/agent client endpoint over http:// sends prompts and responses (and often API keys) in cleartext, exposing them to interception. Use https for the base_url.
Context: base_url="http://test"
Note: [CWE-319] Cleartext Transmission of Sensitive Information.

(llm-client-insecure-http-python)

tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py

[info] 1252-1252: use jsonify instead of json.dumps for JSON output
Context: json.dumps(cls._research_note())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1303-1303: use jsonify instead of json.dumps for JSON output
Context: json.dumps(invalid_note)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1350-1350: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._research_note())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1397-1397: use jsonify instead of json.dumps for JSON output
Context: json.dumps(note)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1414-1425: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{
"path": "/shared/aiq-artifacts/quarterly_revenue.csv",
"kind": "dataset",
"expected_sha256": self._CSV_DIGEST,
}
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1441-1452: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{
"path": "/shared/aiq-artifacts/unknown.csv",
"kind": "dataset",
"expected_sha256": "a" * 64,
}
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

src/aiq_agent/agents/deep_researcher/custom_middleware.py

[warning] 151-151: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.search(boundary_pattern, command)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py

[info] 70-70: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"version": 1, "artifacts": [entry]})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 325-333: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{"path": csv_path, "kind": "table"},
{"path": csv_path, "kind": "dataset", "expected_sha256": "a" * 64},
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 350-358: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{"path": csv_path, "kind": "dataset", "expected_sha256": digest},
{"path": csv_path, "kind": "dataset", "expected_sha256": other_digest},
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 452-460: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{"path": csv_path, "kind": "dataset", "expected_sha256": "a" * 64},
{"path": f"{_ARTIFACT_DIR}/bad.png", "kind": "not-a-kind"},
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 471-471: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"version": 1, "artifacts": []})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 580-580: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"version": 1, "artifacts": entries})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py

[info] 104-104: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"version": 1, "artifacts": artifacts})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 164-164: use jsonify instead of json.dumps for JSON output
Context: json.dumps(note)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🪛 LanguageTool
src/aiq_agent/agents/deep_researcher/skills/synthesis/chart-generation/SKILL.md

[style] ~21-~21: The double modal “Required Input” is nonstandard (only accepted in certain dialects). Consider “to be Input”.
Context: ...ow or value in csv_text. ## Required Input Read every ResearchNotes file and se...

(NEEDS_FIXED)

docs/source/customization/configuration-reference.md

[style] ~495-~495: Consider a different adjective to strengthen your wording.
Context: ... Optional wall-clock deadline for async deep research. When timeout wins the termina...

(DEEP_PROFOUND)

🔇 Additional comments (50)
src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py (1)

24-24: LGTM!

Also applies to: 53-53

src/aiq_agent/agents/deep_researcher/sandbox/README.md (1)

171-177: LGTM!

tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py (1)

67-71: 📐 Maintainability & Code Quality

Run the required Python validation.

Please provide passing Ruff check/format validation and pytest results for these behavior changes. As per coding guidelines, “Run ruff check and ruff format validation for Python code changes” and “Run pytest for all behavior changes in Python code.”

Also applies to: 97-102, 248-259, 277-299, 300-321, 322-343, 344-366, 367-383, 385-402, 403-430, 431-448, 449-477, 478-497, 499-529, 530-543, 545-573, 575-587, 589-603, 604-615, 616-625

Source: Coding guidelines

src/aiq_agent/agents/deep_researcher/tools/research.py (1)

41-98: LGTM!

Also applies to: 143-176, 210-253, 300-300, 327-335

tests/aiq_agent/agents/deep_researcher/models/test_subagent_contracts.py (1)

56-92: LGTM!

Also applies to: 239-318, 320-387

tests/aiq_agent/agents/deep_researcher/test_agent.py (1)

66-123: LGTM!

Also applies to: 275-283, 315-352, 500-514, 575-588, 1480-1745

tests/aiq_agent/agents/deep_researcher/tools/test_research.py (1)

39-139: LGTM!

Also applies to: 142-333

CHANGELOG.md (1)

26-27: LGTM!

README.md (1)

69-69: LGTM!

Also applies to: 288-288, 464-466

docs/source/architecture/agents/sandbox.md (1)

65-71: LGTM!

docs/source/architecture/data-flow.md (1)

44-44: LGTM!

Also applies to: 63-69, 187-191

docs/source/deployment/openshell.md (1)

394-395: LGTM!

Also applies to: 410-415

docs/source/integration/rest-api.md (1)

288-293: LGTM!

tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py (2)

210-338: LGTM!


346-376: LGTM!

tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py (1)

4-146: LGTM!

tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py (4)

921-996: LGTM!


1024-1100: LGTM!


1133-1180: LGTM!


1183-1653: LGTM!

tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py (1)

64-64: LGTM!

Also applies to: 233-241

tests/aiq_agent/agents/deep_researcher/test_factory.py (3)

198-253: LGTM!


290-299: LGTM!

Also applies to: 378-394


452-489: LGTM!

tests/aiq_agent/fastapi_extensions/test_deep_research.py (1)

276-379: LGTM!

configs/config_domain_routing_and_skills.yml (1)

172-172: LGTM!

Also applies to: 199-199

configs/config_openshell.yml (1)

123-126: LGTM!

Also applies to: 164-164

docs/source/architecture/agents/deep-researcher.md (1)

200-201: LGTM!

Also applies to: 221-222, 279-327

docs/source/customization/configuration-reference.md (1)

471-473: LGTM!

Also applies to: 494-495

src/aiq_agent/agents/deep_researcher/skills/synthesis/chart-generation/SKILL.md (1)

1-181: LGTM!

src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py (1)

46-49: LGTM!

Also applies to: 66-79

src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py (1)

56-77: LGTM!

Also applies to: 210-275, 427-541, 596-736

src/aiq_agent/agents/deep_researcher/custom_middleware.py (1)

58-209: LGTM!

Also applies to: 838-1115, 1139-1265

src/aiq_agent/agents/deep_researcher/prompts/orchestrator.j2 (1)

18-19: LGTM!

Also applies to: 137-137

docs/source/examples/skills-sandbox/index.md (1)

87-109: LGTM!

src/aiq_agent/agents/deep_researcher/register.py (1)

112-122: LGTM!

Also applies to: 248-248, 284-284

src/aiq_agent/agents/deep_researcher/agent.py (1)

216-458: LGTM!

src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py (1)

202-419: LGTM!

src/aiq_agent/agents/deep_researcher/models/__init__.py (1)

21-21: LGTM!

Also applies to: 38-38

src/aiq_agent/agents/deep_researcher/skills/research/data-table-analysis/SKILL.md (1)

22-50: LGTM!

Also applies to: 80-99, 128-136, 182-184

src/aiq_agent/agents/deep_researcher/factory.py (1)

277-304: LGTM!

Also applies to: 363-420, 474-630

src/aiq_agent/agents/deep_researcher/prompts/researcher.j2 (1)

54-54: 🎯 Functional Correctness

Prompt context is already passed through Both prompts get execution_enabled, sandbox_workdir, and sandbox_artifact_dir from render_prompt, so the concern about empty paths or a dropped writer block does not apply.

			> Likely an incorrect or invalid review comment.
frontends/aiq_api/src/aiq_api/jobs/runner.py (6)

48-69: LGTM!


264-282: LGTM!


935-963: Terminal-state race handling is coherent. The inner handler claims the timeout terminal state (flags + _handle_workflow_timeout) before external context managers unwind; the outer handler stays idempotent via timeout_terminal_recorded; and the generic handler returns early so a late cleanup failure cannot overwrite the stable timeout reason. finally teardown is correctly gated on sandbox_teardown_started.

Also applies to: 1033-1059, 1077-1086, 1120-1122


1183-1370: LGTM!


83-83: LGTM!

Also applies to: 989-997, 1414-1414, 1490-1490, 1514-1514, 1555-1576


302-377: 🩺 Stability & Availability

Validate workflow_timeout_seconds at the config boundary. run_with_cancellation() rejects non-finite or <= 0 values, so any config path that can pass those through will fail late as a generic ValueError instead of being rejected at load time.

tests/aiq_agent/jobs/test_runner.py (2)

1593-2235: Solid watchdog coverage. Cancellation-vs-timeout distinction, bounded teardown ordering, CAS claim/preserve semantics, and the secret-leak assertion for the generic path are all exercised with finite waits — no hang risk.


2638-2638: LGTM!

Also applies to: 2655-2655, 2667-2667, 2693-2693, 3087-3087, 3220-3221

@AjayThorve AjayThorve added this to the v2.2 milestone Jul 21, 2026
rapids-bot Bot pushed a commit that referenced this pull request Jul 22, 2026
#### Overview

Deep-research currently accepts any non-empty `/shared/output.md` as the final report, including stale planner/orchestrator content from the same state. This prerequisite establishes a small writer-owned commit boundary before the broader convergence/artifact work in #373.

- Create one run-local `FinalReportCommitTracker` per `DeepResearcherAgent.run()`.
- Deny non-writer `write_file`/`edit_file` mutations of `/shared/output.md` and its route-local `/output.md` state alias with `final_report_writer_only`.
- Upsert writer `write_file` calls through the existing backend, refresh the exact UTF-8 SHA-256 after successful edits, and sanitize backend failures as `writer_output_commit_failed`.
- Require a successful writer mutation, non-empty current content, and an exact digest match before writer completion or final extraction.
- Preserve the existing one-turn correction, then fail closed with `writer_output_not_committed`.
- Remove production inline-orchestrator report salvage.
- Document that the proof is run-local and byte-exact; it is not persistent across restart and does not claim cross-provider filesystem atomicity.

This changes no API fields, configuration schema, database schema, deployment behavior, or sandbox-provider implementation.

After this prerequisite merges, #373 can rebase onto `release/2.2` and use its validated ResearchNotes fallback when no committed writer report exists.

#### DCO sign-off for the squash commit

Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>

#### Validation

- [x] `uv run ruff check .` — passed
- [x] `uv run ruff format --check .` — passed (372 files)
- [x] Focused deep-research/job-runner regressions — 265 passed
- [x] `uv run pytest` — 1,836 passed, 13 skipped
- [x] `pre-commit run --all-files` — all 13 hooks passed, including secrets, lockfiles, links, and notebook output
- [x] `cd docs && make html` — passed
- [x] Live Modal smoke — `deep_researcher` reached success with a non-empty 12,351-byte report
- [x] Live OpenShell 0.0.80 local-demo smoke — strict readiness passed; job reached success with a non-empty 7,360-byte report; attestation and cleanup events were recorded; no owned sandbox remained
- [x] I added or updated tests for behavior changes.
- [x] I updated documentation for contributor-facing architecture behavior.
- [x] I confirmed this PR does not include secrets, credentials, local configs, generated policy files, or internal-only data.
- [x] I certify this contribution under the Developer Certificate of Origin (DCO) and signed the commit with `git commit -s`.
- [x] I replaced the DCO sign-off placeholder with my GitHub commit identity and kept the required angle brackets.

#### Where should reviewers start?

1. `FinalReportCommitTracker`, `FinalReportOwnershipGuardMiddleware`, and `FinalReportCommitMiddleware` in `custom_middleware.py`.
2. Per-run tracker construction/extraction in `agent.py` and role-specific wiring in `factory.py`.
3. The routed `StateBackend`/`CompositeBackend` stale-planner overwrite regression in `test_custom_middleware.py`.

#### Related Issues

- Prerequisite for #373.
- Independent of #372; no artifact-capture configuration is included here.



## Summary by CodeRabbit

- **Bug Fixes**
  - Final research reports are now accepted only after the writer commits `/shared/output.md` (or the supported backend alias) during the current run.
  - Commit verification is digest-based, rejecting stale/modified/missing/whitespace-only output and detecting any post-write tampering.
  - Only the designated writer can create/update the final report artifact; completion is blocked until the verified commit occurs.
  - Removed inline fallback/salvage behavior—if the commit proof isn’t present, runs fail with `writer_output_not_committed`.
- **Documentation**
  - Updated deep-research architecture docs to describe the stricter writer/output commit contract, ownership guardrails, and failure-closed handoffs.
- **Tests**
  - Expanded coverage for digest-verified commit tracking, writer-only ownership enforcement, and updated failure reason/validation scenarios.

Authors:
  - Kyle Zheng (https://github.com/KyleZheng1284)
  - Ajay Thorve (https://github.com/AjayThorve)

Approvers:
  - Ajay Thorve (https://github.com/AjayThorve)

URL: #375
@KyleZheng1284
KyleZheng1284 force-pushed the fix/deep-research-convergence branch from 2f51e04 to 515a4cb Compare July 22, 2026 20:20
@copy-pr-bot

copy-pr-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@KyleZheng1284
KyleZheng1284 force-pushed the fix/deep-research-convergence branch from 515a4cb to 209feaf Compare July 22, 2026 20:24
@KyleZheng1284
KyleZheng1284 marked this pull request as ready for review July 22, 2026 20:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aiq_agent/agents/deep_researcher/register.py`:
- Around line 254-255: Update the async deep-research factory registration to
forward config.max_researcher_execute_attempts alongside
max_writer_execute_attempts. Ensure the use_async_deep_research job path
receives the configured researcher retry limit instead of relying on the
default.

In `@src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py`:
- Around line 73-76: The helper _manifest_error_is_not_found must stop inferring
absence from arbitrary normalized error text or substring markers. Update the
download_files error flow to use a structured not-found signal, or match only
the backend’s explicit not-found enum, while preserving fail-closed behavior for
unreadable or other backend failures and only enabling scan fallback for
confirmed absence.

In
`@src/aiq_agent/agents/deep_researcher/skills/research/data-table-analysis/SKILL.md`:
- Around line 47-50: Update the dataset constraints in SKILL.md to document the
aggregate per-note CSV limit enforced by
ResearchNotes._validate_quantitative_dataset_references: all datasets in one
note together must not exceed 128 KiB of UTF-8 CSV, in addition to the existing
per-dataset limits.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 1c213ad3-d2d4-4670-88a1-67ce19674aec

📥 Commits

Reviewing files that changed from the base of the PR and between 2f51e04 and 209feaf.

📒 Files selected for processing (40)
  • CHANGELOG.md
  • README.md
  • configs/config_domain_routing_and_skills.yml
  • configs/config_openshell.yml
  • docs/source/architecture/agents/deep-researcher.md
  • docs/source/architecture/agents/sandbox.md
  • docs/source/architecture/data-flow.md
  • docs/source/customization/configuration-reference.md
  • docs/source/deployment/openshell.md
  • docs/source/examples/skills-sandbox/index.md
  • docs/source/integration/rest-api.md
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
  • src/aiq_agent/agents/deep_researcher/factory.py
  • src/aiq_agent/agents/deep_researcher/models/__init__.py
  • src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py
  • src/aiq_agent/agents/deep_researcher/prompts/orchestrator.j2
  • src/aiq_agent/agents/deep_researcher/prompts/researcher.j2
  • src/aiq_agent/agents/deep_researcher/prompts/writer.j2
  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/sandbox/README.md
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py
  • src/aiq_agent/agents/deep_researcher/skills/research/chart-generation/SKILL.md
  • src/aiq_agent/agents/deep_researcher/skills/research/data-table-analysis/SKILL.md
  • src/aiq_agent/agents/deep_researcher/skills/synthesis/chart-generation/SKILL.md
  • src/aiq_agent/agents/deep_researcher/tools/research.py
  • tests/aiq_agent/agents/deep_researcher/models/test_subagent_contracts.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py
  • tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py
  • tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/agents/deep_researcher/test_factory.py
  • tests/aiq_agent/agents/deep_researcher/tools/test_research.py
  • tests/aiq_agent/fastapi_extensions/test_deep_research.py
  • tests/aiq_agent/jobs/test_runner.py
💤 Files with no reviewable changes (1)
  • src/aiq_agent/agents/deep_researcher/skills/research/chart-generation/SKILL.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (8)
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run ruff check and ruff format validation for Python code changes

**/*.py: Python code must be linted and formatted with Ruff using line length 120, target Python 3.11, rule sets E,F,W,I,PL,UP, and isort force-single-line configuration
Never commit secrets, tokens, or environment-specific hostnames; use environment variables and SecretStr instead, resolving API keys at runtime
Never print or log secret values, including in tool output or error messages
Missing-secret paths must degrade gracefully (stub/skip), not crash or leak
Do not hand-reformat unrelated code when making changes; match the existing import and formatting style

Files:

  • src/aiq_agent/agents/deep_researcher/models/__init__.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/fastapi_extensions/test_deep_research.py
  • src/aiq_agent/agents/deep_researcher/register.py
  • tests/aiq_agent/agents/deep_researcher/models/test_subagent_contracts.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py
  • tests/aiq_agent/jobs/test_runner.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • tests/aiq_agent/agents/deep_researcher/test_factory.py
  • tests/aiq_agent/agents/deep_researcher/tools/test_research.py
  • src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • src/aiq_agent/agents/deep_researcher/tools/research.py
  • tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py
  • tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py
  • tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
  • src/aiq_agent/agents/deep_researcher/factory.py
src/aiq_agent/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/aiq_agent/**/*.py: Respect authenticated data sources by honoring requires_auth, per-user token pass-through, and backend token validators; apply owner guardrails before loading protected report or artifact context into an agent
Do not weaken or bypass AuthMiddleware, validators, or auth gating without a prior design discussion

Files:

  • src/aiq_agent/agents/deep_researcher/models/__init__.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py
  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py
  • src/aiq_agent/agents/deep_researcher/tools/research.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
  • src/aiq_agent/agents/deep_researcher/factory.py
src/aiq_agent/agents/**/*

⚙️ CodeRabbit configuration file

src/aiq_agent/agents/**/*: Review agent changes for research workflow correctness, graph state transitions, prompt/tool contracts,
HITL behavior, and failure handling. Flag changes that weaken source attribution, report generation,
async cancellation, checkpointing, or data-source selection without focused tests and docs.

Files:

  • src/aiq_agent/agents/deep_researcher/models/__init__.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py
  • src/aiq_agent/agents/deep_researcher/sandbox/README.md
  • src/aiq_agent/agents/deep_researcher/skills/synthesis/chart-generation/SKILL.md
  • src/aiq_agent/agents/deep_researcher/prompts/researcher.j2
  • src/aiq_agent/agents/deep_researcher/prompts/orchestrator.j2
  • src/aiq_agent/agents/deep_researcher/prompts/writer.j2
  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py
  • src/aiq_agent/agents/deep_researcher/skills/research/data-table-analysis/SKILL.md
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py
  • src/aiq_agent/agents/deep_researcher/tools/research.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
  • src/aiq_agent/agents/deep_researcher/factory.py
docs/source/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Update the docs under docs/source/ when behavior, configuration, or workflows change

Files:

  • docs/source/architecture/agents/sandbox.md
  • docs/source/deployment/openshell.md
  • docs/source/integration/rest-api.md
  • docs/source/architecture/agents/deep-researcher.md
  • docs/source/customization/configuration-reference.md
  • docs/source/architecture/data-flow.md
  • docs/source/examples/skills-sandbox/index.md
{docs/**,README.md,CONTRIBUTING.md,SECURITY.md,CODE-OF-CONDUCT.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,CONTRIBUTING.md,SECURITY.md,CODE-OF-CONDUCT.md}: Review documentation for command accuracy, branch-name consistency, current CI and copy-pr-bot behavior, public
vs internal boundary clarity, stale examples, and links that no longer match the repository layout.

Files:

  • docs/source/architecture/agents/sandbox.md
  • docs/source/deployment/openshell.md
  • docs/source/integration/rest-api.md
  • docs/source/architecture/agents/deep-researcher.md
  • docs/source/customization/configuration-reference.md
  • README.md
  • docs/source/architecture/data-flow.md
  • docs/source/examples/skills-sandbox/index.md
{deploy/**,configs/**}

⚙️ CodeRabbit configuration file

{deploy/**,configs/**}: Review deployment and config changes for secret separation, safe defaults, local-vs-production behavior, Helm and
Docker portability, and documentation parity. Flag committed credentials, environment-specific NVIDIA internals in
public defaults, and changes that make examples diverge from CI-tested paths.

Files:

  • configs/config_domain_routing_and_skills.yml
  • configs/config_openshell.yml
**/*test*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run pytest for all behavior changes in Python code

Files:

  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/fastapi_extensions/test_deep_research.py
  • tests/aiq_agent/agents/deep_researcher/models/test_subagent_contracts.py
  • tests/aiq_agent/jobs/test_runner.py
  • tests/aiq_agent/agents/deep_researcher/test_factory.py
  • tests/aiq_agent/agents/deep_researcher/tools/test_research.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py
  • tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py
  • tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py
{src/aiq_agent/fastapi_extensions/**,frontends/aiq_api/src/aiq_api/**}

⚙️ CodeRabbit configuration file

{src/aiq_agent/fastapi_extensions/**,frontends/aiq_api/src/aiq_api/**}: Treat API, auth, and job-runner changes as externally visible contracts. Check authorization boundaries,
request tracing, async job lifecycle, websocket reconnect behavior, error responses, and cross-user data isolation.
Require tests for route behavior, access decisions, and job state transitions when those surfaces change.

Files:

  • frontends/aiq_api/src/aiq_api/jobs/runner.py
🧠 Learnings (1)
📚 Learning: 2026-07-06T23:55:42.908Z
Learnt from: cdgamarose-nv
Repo: NVIDIA-AI-Blueprints/aiq PR: 311
File: src/aiq_agent/agents/deep_researcher/prompts/orchestrator.j2:74-81
Timestamp: 2026-07-06T23:55:42.908Z
Learning: In agent test files (e.g., tests/aiq_agent/agents/*/test_agent.py), avoid brittle assertions that match exact substrings from prompt template files (such as *.j2 prompt wording). Prompt wording can change frequently, so instead assert structural/behavioral properties (e.g., that the prompt builder is called, that required sections/fields are present via stable markers, that the model output/agent behavior conforms to an expected schema, or that key actions are taken) rather than matching literal prompt text.

Applied to files:

  • tests/aiq_agent/agents/deep_researcher/test_agent.py
🪛 ast-grep (0.44.1)
tests/aiq_agent/fastapi_extensions/test_deep_research.py

[warning] 368-368: Do not make http calls without encryption
Context: "http://test"
Note: [CWE-319] Cleartext Transmission of Sensitive Information.

(requests-http)


[warning] 368-368: Configuring an LLM/agent client endpoint over http:// sends prompts and responses (and often API keys) in cleartext, exposing them to interception. Use https for the base_url.
Context: base_url="http://test"
Note: [CWE-319] Cleartext Transmission of Sensitive Information.

(llm-client-insecure-http-python)


[info] 296-308: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{
"path": csv_path,
"kind": "dataset",
"inline": False,
"expected_sha256": digest,
}
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py

[info] 104-104: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"version": 1, "artifacts": artifacts})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 164-164: use jsonify instead of json.dumps for JSON output
Context: json.dumps(note)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py

[info] 1559-1559: use jsonify instead of json.dumps for JSON output
Context: json.dumps(cls._research_note())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1608-1608: use jsonify instead of json.dumps for JSON output
Context: json.dumps(invalid_note)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1685-1685: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._research_note())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1732-1732: use jsonify instead of json.dumps for JSON output
Context: json.dumps(note)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1749-1760: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{
"path": "/shared/aiq-artifacts/quarterly_revenue.csv",
"kind": "dataset",
"expected_sha256": self._CSV_DIGEST,
}
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1776-1787: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{
"path": "/shared/aiq-artifacts/unknown.csv",
"kind": "dataset",
"expected_sha256": "a" * 64,
}
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1803-1808: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [{"path": "/shared/aiq-artifacts/report.pdf", "kind": "document"}],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py

[info] 70-70: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"version": 1, "artifacts": [entry]})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 366-374: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{"path": csv_path, "kind": "table"},
{"path": csv_path, "kind": "dataset", "expected_sha256": "a" * 64},
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 391-399: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{"path": csv_path, "kind": "dataset", "expected_sha256": digest},
{"path": csv_path, "kind": "dataset", "expected_sha256": other_digest},
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 493-501: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{"path": csv_path, "kind": "dataset", "expected_sha256": "a" * 64},
{"path": f"{_ARTIFACT_DIR}/bad.png", "kind": "not-a-kind"},
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 512-512: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"version": 1, "artifacts": []})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 621-621: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"version": 1, "artifacts": entries})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

src/aiq_agent/agents/deep_researcher/custom_middleware.py

[warning] 177-177: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.search(boundary_pattern, command)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

🪛 LanguageTool
src/aiq_agent/agents/deep_researcher/skills/synthesis/chart-generation/SKILL.md

[style] ~21-~21: The double modal “Required Input” is nonstandard (only accepted in certain dialects). Consider “to be Input”.
Context: ...ow or value in csv_text. ## Required Input Read every ResearchNotes file and se...

(NEEDS_FIXED)

docs/source/customization/configuration-reference.md

[style] ~497-~497: Consider a different adjective to strengthen your wording.
Context: ... Optional wall-clock deadline for async deep research. When timeout wins the termina...

(DEEP_PROFOUND)

🔇 Additional comments (44)
CHANGELOG.md (1)

26-27: LGTM!

README.md (1)

69-69: LGTM!

Also applies to: 288-288, 464-466

docs/source/architecture/agents/sandbox.md (1)

12-13: LGTM!

Also applies to: 66-72

src/aiq_agent/agents/deep_researcher/sandbox/README.md (1)

156-156: LGTM!

Also applies to: 172-178

tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py (3)

58-70: LGTM!


73-96: LGTM!


258-266: LGTM!

Also applies to: 363-390

tests/aiq_agent/fastapi_extensions/test_deep_research.py (2)

55-60: LGTM!


276-381: LGTM!

docs/source/architecture/data-flow.md (1)

44-69: LGTM!

Also applies to: 187-192

docs/source/deployment/openshell.md (1)

120-138: LGTM!

Also applies to: 190-190, 407-428

docs/source/integration/rest-api.md (1)

288-293: LGTM!

tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py (1)

56-105: LGTM!

Also applies to: 169-196, 210-338, 346-376

tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py (1)

4-63: LGTM!

Also applies to: 65-147

tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py (1)

18-56: LGTM!

Also applies to: 294-472, 475-649, 1162-1341, 1374-1488, 1490-2022

tests/aiq_agent/agents/deep_researcher/test_factory.py (1)

24-56: LGTM!

Also applies to: 116-141, 204-264, 301-334, 376-448, 470-543

src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py (1)

46-49: LGTM!

Also applies to: 66-79

src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py (1)

56-72: LGTM!

Also applies to: 210-299, 424-565, 592-757, 759-802

src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py (1)

24-24: LGTM!

Also applies to: 53-53

frontends/aiq_api/src/aiq_api/jobs/runner.py (1)

48-69: LGTM!

Also applies to: 83-83, 264-281, 298-377, 935-963, 989-997, 1033-1122, 1183-1189, 1212-1368, 1414-1414, 1490-1490, 1514-1514, 1555-1575

tests/aiq_agent/jobs/test_runner.py (1)

73-73: LGTM!

Also applies to: 1594-2235, 2639-2694, 3088-3088, 3221-3222

tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py (1)

67-71: LGTM!

Also applies to: 97-102, 248-300, 318-681

src/aiq_agent/agents/deep_researcher/custom_middleware.py (2)

1422-1441: Newline normalization now applied to both baseline and tables — the prior CRLF-mismatch concern is resolved.


65-116: LGTM!

Also applies to: 131-235, 1264-1525

configs/config_domain_routing_and_skills.yml (1)

172-172: LGTM!

Also applies to: 186-189, 203-204

configs/config_openshell.yml (1)

123-126: LGTM!

Also applies to: 165-166

docs/source/architecture/agents/deep-researcher.md (1)

207-209: LGTM!

Also applies to: 229-231

src/aiq_agent/agents/deep_researcher/prompts/writer.j2 (1)

6-17: LGTM!

Also applies to: 60-73, 97-126, 147-148

src/aiq_agent/agents/deep_researcher/register.py (1)

113-128: LGTM!

src/aiq_agent/agents/deep_researcher/factory.py (1)

296-320: LGTM!

Also applies to: 373-431, 484-542, 604-653

src/aiq_agent/agents/deep_researcher/agent.py (1)

222-245: LGTM!

Also applies to: 247-336, 337-456

tests/aiq_agent/agents/deep_researcher/test_agent.py (1)

286-296: LGTM!

Also applies to: 344-370, 518-532, 593-606, 1573-1802

tests/aiq_agent/agents/deep_researcher/tools/test_research.py (1)

90-141: LGTM!

Also applies to: 143-159, 251-326, 339-345

docs/source/customization/configuration-reference.md (1)

471-474: LGTM!

Also applies to: 495-497

docs/source/examples/skills-sandbox/index.md (1)

9-11: LGTM!

Also applies to: 26-31, 74-74, 95-95, 109-118, 128-130, 166-172, 182-191, 224-231, 236-256

src/aiq_agent/agents/deep_researcher/skills/research/data-table-analysis/SKILL.md (1)

22-46: LGTM!

Also applies to: 80-99, 128-136, 182-184

src/aiq_agent/agents/deep_researcher/skills/synthesis/chart-generation/SKILL.md (1)

1-181: LGTM!

src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py (1)

18-44: LGTM!

Also applies to: 202-367, 383-419

src/aiq_agent/agents/deep_researcher/models/__init__.py (1)

21-21: LGTM!

Also applies to: 38-38

src/aiq_agent/agents/deep_researcher/prompts/orchestrator.j2 (1)

18-19: LGTM!

Also applies to: 137-137

src/aiq_agent/agents/deep_researcher/prompts/researcher.j2 (1)

46-55: LGTM!

Also applies to: 80-81

src/aiq_agent/agents/deep_researcher/tools/research.py (2)

25-99: LGTM!

Also applies to: 144-178, 212-263, 310-310


337-345: 🗄️ Data Integrity & Integration

No issue: this branch is unreachable from the current wiring. artifact_manager is only created when a sandbox provider and artifact_db_url are present, and context.backend always resolves to a concrete backend (StateBackend or CompositeBackend), never None.

			> Likely an incorrect or invalid review comment.
tests/aiq_agent/agents/deep_researcher/models/test_subagent_contracts.py (1)

18-26: LGTM!

Also applies to: 56-92, 239-318, 320-387

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/aiq_agent/agents/deep_researcher/agent.py (1)

273-276: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail closed when a published digest has no validated dataset.

Filtering candidates drops unmatched published digests; required_tables then becomes empty and Line 241 accepts a committed report without any canonical table. Treat a published digest missing from validated ResearchNotes as an invalid writer output/fallback state instead of silently accepting it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiq_agent/agents/deep_researcher/agent.py` around lines 273 - 276, Update
the candidate filtering flow around _published_canonical_digests so every
published digest must match a validated ResearchNotes dataset. Detect unmatched
published digests before allowing required_tables to be evaluated, and mark the
writer output as invalid or trigger the existing fallback path instead of
proceeding with an empty requirement. Preserve normal filtering when all
published digests have validated candidates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/aiq_agent/agents/deep_researcher/agent.py`:
- Around line 273-276: Update the candidate filtering flow around
_published_canonical_digests so every published digest must match a validated
ResearchNotes dataset. Detect unmatched published digests before allowing
required_tables to be evaluated, and mark the writer output as invalid or
trigger the existing fallback path instead of proceeding with an empty
requirement. Preserve normal filtering when all published digests have validated
candidates.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 1c26ae70-b6ca-4203-b042-44a5e95c7a9b

📥 Commits

Reviewing files that changed from the base of the PR and between 209feaf and 1da07f1.

📒 Files selected for processing (15)
  • configs/config_domain_routing_and_skills.yml
  • configs/config_openshell.yml
  • docs/source/architecture/agents/deep-researcher.md
  • docs/source/customization/configuration-reference.md
  • docs/source/examples/skills-sandbox/index.md
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
  • src/aiq_agent/agents/deep_researcher/factory.py
  • src/aiq_agent/agents/deep_researcher/prompts/researcher.j2
  • src/aiq_agent/agents/deep_researcher/register.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py
  • tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/agents/deep_researcher/test_factory.py
💤 Files with no reviewable changes (3)
  • configs/config_domain_routing_and_skills.yml
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • configs/config_openshell.yml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
src/aiq_agent/agents/**/*

⚙️ CodeRabbit configuration file

src/aiq_agent/agents/**/*: Review agent changes for research workflow correctness, graph state transitions, prompt/tool contracts,
HITL behavior, and failure handling. Flag changes that weaken source attribution, report generation,
async cancellation, checkpointing, or data-source selection without focused tests and docs.

Files:

  • src/aiq_agent/agents/deep_researcher/prompts/researcher.j2
  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/factory.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
docs/source/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Update the docs under docs/source/ when behavior, configuration, or workflows change

Files:

  • docs/source/customization/configuration-reference.md
  • docs/source/architecture/agents/deep-researcher.md
  • docs/source/examples/skills-sandbox/index.md
{docs/**,README.md,CONTRIBUTING.md,SECURITY.md,CODE-OF-CONDUCT.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,CONTRIBUTING.md,SECURITY.md,CODE-OF-CONDUCT.md}: Review documentation for command accuracy, branch-name consistency, current CI and copy-pr-bot behavior, public
vs internal boundary clarity, stale examples, and links that no longer match the repository layout.

Files:

  • docs/source/customization/configuration-reference.md
  • docs/source/architecture/agents/deep-researcher.md
  • docs/source/examples/skills-sandbox/index.md
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run ruff check and ruff format validation for Python code changes

**/*.py: Python code must be linted and formatted with Ruff using line length 120, target Python 3.11, rule sets E,F,W,I,PL,UP, and isort force-single-line configuration
Never commit secrets, tokens, or environment-specific hostnames; use environment variables and SecretStr instead, resolving API keys at runtime
Never print or log secret values, including in tool output or error messages
Missing-secret paths must degrade gracefully (stub/skip), not crash or leak
Do not hand-reformat unrelated code when making changes; match the existing import and formatting style

Files:

  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py
  • tests/aiq_agent/agents/deep_researcher/test_factory.py
  • tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py
  • src/aiq_agent/agents/deep_researcher/factory.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
src/aiq_agent/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/aiq_agent/**/*.py: Respect authenticated data sources by honoring requires_auth, per-user token pass-through, and backend token validators; apply owner guardrails before loading protected report or artifact context into an agent
Do not weaken or bypass AuthMiddleware, validators, or auth gating without a prior design discussion

Files:

  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/factory.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
**/*test*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run pytest for all behavior changes in Python code

Files:

  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py
  • tests/aiq_agent/agents/deep_researcher/test_factory.py
  • tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py
🧠 Learnings (1)
📚 Learning: 2026-07-06T23:55:42.908Z
Learnt from: cdgamarose-nv
Repo: NVIDIA-AI-Blueprints/aiq PR: 311
File: src/aiq_agent/agents/deep_researcher/prompts/orchestrator.j2:74-81
Timestamp: 2026-07-06T23:55:42.908Z
Learning: In agent test files (e.g., tests/aiq_agent/agents/*/test_agent.py), avoid brittle assertions that match exact substrings from prompt template files (such as *.j2 prompt wording). Prompt wording can change frequently, so instead assert structural/behavioral properties (e.g., that the prompt builder is called, that required sections/fields are present via stable markers, that the model output/agent behavior conforms to an expected schema, or that key actions are taken) rather than matching literal prompt text.

Applied to files:

  • tests/aiq_agent/agents/deep_researcher/test_agent.py
🔇 Additional comments (12)
tests/aiq_agent/agents/deep_researcher/test_factory.py (1)

24-42: LGTM!

Also applies to: 56-56, 205-205, 214-287, 324-333, 455-471, 529-566

tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py (1)

4-23: LGTM!

Also applies to: 25-39, 41-54, 56-63, 65-101, 103-124, 126-146, 148-167

src/aiq_agent/agents/deep_researcher/prompts/researcher.j2 (1)

46-48: LGTM!

Also applies to: 52-57, 82-83

docs/source/architecture/agents/deep-researcher.md (1)

207-209: LGTM!

Also applies to: 234-257, 342-369

docs/source/customization/configuration-reference.md (1)

471-474: LGTM!

Also applies to: 495-510

docs/source/examples/skills-sandbox/index.md (1)

9-11: LGTM!

Also applies to: 26-31, 74-74, 108-117, 127-129, 165-171, 181-190, 223-255

src/aiq_agent/agents/deep_researcher/register.py (1)

113-128: LGTM!

Also applies to: 254-255, 291-292

src/aiq_agent/agents/deep_researcher/factory.py (1)

147-147: LGTM!

Also applies to: 215-233, 280-315, 374-415, 424-431, 485-508, 561-652

src/aiq_agent/agents/deep_researcher/custom_middleware.py (1)

1280-1285: LGTM!

Also applies to: 1436-1441, 1454-1525, 1535-1561

tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py (1)

1181-1235: LGTM!

Also applies to: 1281-1421, 1479-1487, 1928-1938

src/aiq_agent/agents/deep_researcher/agent.py (1)

56-57: LGTM!

Also applies to: 94-219

tests/aiq_agent/agents/deep_researcher/test_agent.py (1)

286-369: LGTM!

Also applies to: 518-606, 1573-1843

Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
@KyleZheng1284
KyleZheng1284 force-pushed the fix/deep-research-convergence branch from 1da07f1 to 7f72f47 Compare July 23, 2026 17:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py`:
- Around line 332-366: Update _validate_csv_quote_placement to handle the
after_quote state with an explicit state == after_quote branch before its
transitions. Preserve the existing behavior: a following quote returns to
quoted, a delimiter or line break starts a new field, and any other character
raises ValueError.

In `@src/aiq_agent/agents/deep_researcher/sandbox/README.md`:
- Around line 174-178: Clarify the checkpointing paragraph in the README so
“terminal finalization” explicitly applies to non-cancelled success or failure,
while cancellation is an exception: a busy sandbox may skip the manifest and
directory scan and request immediate termination. Preserve the existing behavior
for an immediately available provider operation lease and retained writer
outputs.

In `@tests/aiq_agent/fastapi_extensions/test_deep_research.py`:
- Around line 314-322: Update the download_files stub’s missing-file error value
to the structured "file_not_found" token expected by
manager._manifest_error_is_not_found, matching the sibling test doubles. Keep
successful file entries unchanged while ensuring absent paths produce the exact
not-found signal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 673c8fd9-95a2-4cfe-9a34-e1cf1f9c592f

📥 Commits

Reviewing files that changed from the base of the PR and between 1da07f1 and 7f72f47.

📒 Files selected for processing (40)
  • CHANGELOG.md
  • README.md
  • configs/config_domain_routing_and_skills.yml
  • configs/config_openshell.yml
  • docs/source/architecture/agents/deep-researcher.md
  • docs/source/architecture/agents/sandbox.md
  • docs/source/architecture/data-flow.md
  • docs/source/customization/configuration-reference.md
  • docs/source/deployment/openshell.md
  • docs/source/examples/skills-sandbox/index.md
  • docs/source/integration/rest-api.md
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
  • src/aiq_agent/agents/deep_researcher/factory.py
  • src/aiq_agent/agents/deep_researcher/models/__init__.py
  • src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py
  • src/aiq_agent/agents/deep_researcher/prompts/orchestrator.j2
  • src/aiq_agent/agents/deep_researcher/prompts/researcher.j2
  • src/aiq_agent/agents/deep_researcher/prompts/writer.j2
  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/sandbox/README.md
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py
  • src/aiq_agent/agents/deep_researcher/skills/research/chart-generation/SKILL.md
  • src/aiq_agent/agents/deep_researcher/skills/research/data-table-analysis/SKILL.md
  • src/aiq_agent/agents/deep_researcher/skills/synthesis/chart-generation/SKILL.md
  • src/aiq_agent/agents/deep_researcher/tools/research.py
  • tests/aiq_agent/agents/deep_researcher/models/test_subagent_contracts.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py
  • tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py
  • tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/agents/deep_researcher/test_factory.py
  • tests/aiq_agent/agents/deep_researcher/tools/test_research.py
  • tests/aiq_agent/fastapi_extensions/test_deep_research.py
  • tests/aiq_agent/jobs/test_runner.py
💤 Files with no reviewable changes (1)
  • src/aiq_agent/agents/deep_researcher/skills/research/chart-generation/SKILL.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (8)
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run ruff check and ruff format validation for Python code changes

**/*.py: Python code must be linted and formatted with Ruff using line length 120, target Python 3.11, rule sets E,F,W,I,PL,UP, and isort force-single-line configuration
Never commit secrets, tokens, or environment-specific hostnames; use environment variables and SecretStr instead, resolving API keys at runtime
Never print or log secret values, including in tool output or error messages
Missing-secret paths must degrade gracefully (stub/skip), not crash or leak
Do not hand-reformat unrelated code when making changes; match the existing import and formatting style

Files:

  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py
  • src/aiq_agent/agents/deep_researcher/models/__init__.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/fastapi_extensions/test_deep_research.py
  • tests/aiq_agent/agents/deep_researcher/models/test_subagent_contracts.py
  • src/aiq_agent/agents/deep_researcher/register.py
  • tests/aiq_agent/jobs/test_runner.py
  • tests/aiq_agent/agents/deep_researcher/tools/test_research.py
  • tests/aiq_agent/agents/deep_researcher/test_factory.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/tools/research.py
  • tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py
  • tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
  • tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py
  • src/aiq_agent/agents/deep_researcher/factory.py
src/aiq_agent/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/aiq_agent/**/*.py: Respect authenticated data sources by honoring requires_auth, per-user token pass-through, and backend token validators; apply owner guardrails before loading protected report or artifact context into an agent
Do not weaken or bypass AuthMiddleware, validators, or auth gating without a prior design discussion

Files:

  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py
  • src/aiq_agent/agents/deep_researcher/models/__init__.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py
  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/tools/research.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
  • src/aiq_agent/agents/deep_researcher/factory.py
src/aiq_agent/agents/**/*

⚙️ CodeRabbit configuration file

src/aiq_agent/agents/**/*: Review agent changes for research workflow correctness, graph state transitions, prompt/tool contracts,
HITL behavior, and failure handling. Flag changes that weaken source attribution, report generation,
async cancellation, checkpointing, or data-source selection without focused tests and docs.

Files:

  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py
  • src/aiq_agent/agents/deep_researcher/prompts/orchestrator.j2
  • src/aiq_agent/agents/deep_researcher/prompts/researcher.j2
  • src/aiq_agent/agents/deep_researcher/models/__init__.py
  • src/aiq_agent/agents/deep_researcher/sandbox/README.md
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py
  • src/aiq_agent/agents/deep_researcher/prompts/writer.j2
  • src/aiq_agent/agents/deep_researcher/skills/synthesis/chart-generation/SKILL.md
  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/skills/research/data-table-analysis/SKILL.md
  • src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/tools/research.py
  • src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py
  • src/aiq_agent/agents/deep_researcher/custom_middleware.py
  • src/aiq_agent/agents/deep_researcher/factory.py
docs/source/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Update the docs under docs/source/ when behavior, configuration, or workflows change

Files:

  • docs/source/integration/rest-api.md
  • docs/source/architecture/agents/sandbox.md
  • docs/source/architecture/data-flow.md
  • docs/source/deployment/openshell.md
  • docs/source/architecture/agents/deep-researcher.md
  • docs/source/customization/configuration-reference.md
  • docs/source/examples/skills-sandbox/index.md
{docs/**,README.md,CONTRIBUTING.md,SECURITY.md,CODE-OF-CONDUCT.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,CONTRIBUTING.md,SECURITY.md,CODE-OF-CONDUCT.md}: Review documentation for command accuracy, branch-name consistency, current CI and copy-pr-bot behavior, public
vs internal boundary clarity, stale examples, and links that no longer match the repository layout.

Files:

  • docs/source/integration/rest-api.md
  • docs/source/architecture/agents/sandbox.md
  • README.md
  • docs/source/architecture/data-flow.md
  • docs/source/deployment/openshell.md
  • docs/source/architecture/agents/deep-researcher.md
  • docs/source/customization/configuration-reference.md
  • docs/source/examples/skills-sandbox/index.md
{deploy/**,configs/**}

⚙️ CodeRabbit configuration file

{deploy/**,configs/**}: Review deployment and config changes for secret separation, safe defaults, local-vs-production behavior, Helm and
Docker portability, and documentation parity. Flag committed credentials, environment-specific NVIDIA internals in
public defaults, and changes that make examples diverge from CI-tested paths.

Files:

  • configs/config_domain_routing_and_skills.yml
  • configs/config_openshell.yml
**/*test*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run pytest for all behavior changes in Python code

Files:

  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/fastapi_extensions/test_deep_research.py
  • tests/aiq_agent/agents/deep_researcher/models/test_subagent_contracts.py
  • tests/aiq_agent/jobs/test_runner.py
  • tests/aiq_agent/agents/deep_researcher/tools/test_research.py
  • tests/aiq_agent/agents/deep_researcher/test_factory.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py
  • tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py
  • tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py
{src/aiq_agent/fastapi_extensions/**,frontends/aiq_api/src/aiq_api/**}

⚙️ CodeRabbit configuration file

{src/aiq_agent/fastapi_extensions/**,frontends/aiq_api/src/aiq_api/**}: Treat API, auth, and job-runner changes as externally visible contracts. Check authorization boundaries,
request tracing, async job lifecycle, websocket reconnect behavior, error responses, and cross-user data isolation.
Require tests for route behavior, access decisions, and job state transitions when those surfaces change.

Files:

  • frontends/aiq_api/src/aiq_api/jobs/runner.py
🧠 Learnings (1)
📚 Learning: 2026-07-06T23:55:42.908Z
Learnt from: cdgamarose-nv
Repo: NVIDIA-AI-Blueprints/aiq PR: 311
File: src/aiq_agent/agents/deep_researcher/prompts/orchestrator.j2:74-81
Timestamp: 2026-07-06T23:55:42.908Z
Learning: In agent test files (e.g., tests/aiq_agent/agents/*/test_agent.py), avoid brittle assertions that match exact substrings from prompt template files (such as *.j2 prompt wording). Prompt wording can change frequently, so instead assert structural/behavioral properties (e.g., that the prompt builder is called, that required sections/fields are present via stable markers, that the model output/agent behavior conforms to an expected schema, or that key actions are taken) rather than matching literal prompt text.

Applied to files:

  • tests/aiq_agent/agents/deep_researcher/test_agent.py
🪛 ast-grep (0.44.1)
tests/aiq_agent/fastapi_extensions/test_deep_research.py

[info] 296-308: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{
"path": csv_path,
"kind": "dataset",
"inline": False,
"expected_sha256": digest,
}
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[warning] 368-368: Do not make http calls without encryption
Context: "http://test"
Note: [CWE-319] Cleartext Transmission of Sensitive Information.

(requests-http)


[warning] 368-368: Configuring an LLM/agent client endpoint over http:// sends prompts and responses (and often API keys) in cleartext, exposing them to interception. Use https for the base_url.
Context: base_url="http://test"
Note: [CWE-319] Cleartext Transmission of Sensitive Information.

(llm-client-insecure-http-python)

tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py

[info] 1559-1559: use jsonify instead of json.dumps for JSON output
Context: json.dumps(cls._research_note())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1608-1608: use jsonify instead of json.dumps for JSON output
Context: json.dumps(invalid_note)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1685-1685: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._research_note())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1732-1732: use jsonify instead of json.dumps for JSON output
Context: json.dumps(note)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1749-1760: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{
"path": "/shared/aiq-artifacts/quarterly_revenue.csv",
"kind": "dataset",
"expected_sha256": self._CSV_DIGEST,
}
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1776-1787: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{
"path": "/shared/aiq-artifacts/unknown.csv",
"kind": "dataset",
"expected_sha256": "a" * 64,
}
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1803-1808: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [{"path": "/shared/aiq-artifacts/report.pdf", "kind": "document"}],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py

[info] 70-70: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"version": 1, "artifacts": [entry]})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 366-374: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{"path": csv_path, "kind": "table"},
{"path": csv_path, "kind": "dataset", "expected_sha256": "a" * 64},
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 391-399: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{"path": csv_path, "kind": "dataset", "expected_sha256": digest},
{"path": csv_path, "kind": "dataset", "expected_sha256": other_digest},
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 495-503: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"version": 1,
"artifacts": [
{"path": csv_path, "kind": "dataset", "expected_sha256": "a" * 64},
{"path": f"{_ARTIFACT_DIR}/bad.png", "kind": "not-a-kind"},
],
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 514-514: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"version": 1, "artifacts": []})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 623-623: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"version": 1, "artifacts": entries})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

src/aiq_agent/agents/deep_researcher/custom_middleware.py

[warning] 177-177: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.search(boundary_pattern, command)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py

[info] 108-108: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"version": 1, "artifacts": artifacts})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 168-168: use jsonify instead of json.dumps for JSON output
Context: json.dumps(note)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🪛 LanguageTool
src/aiq_agent/agents/deep_researcher/skills/synthesis/chart-generation/SKILL.md

[style] ~21-~21: The double modal “Required Input” is nonstandard (only accepted in certain dialects). Consider “to be Input”.
Context: ...ow or value in csv_text. ## Required Input Read every ResearchNotes file and se...

(NEEDS_FIXED)

docs/source/customization/configuration-reference.md

[style] ~497-~497: Consider a different adjective to strengthen your wording.
Context: ... Optional wall-clock deadline for async deep research. When timeout wins the termina...

(DEEP_PROFOUND)

🔇 Additional comments (51)
configs/config_domain_routing_and_skills.yml (1)

170-172: LGTM!

configs/config_openshell.yml (1)

122-126: LGTM!

docs/source/architecture/agents/deep-researcher.md (1)

207-209: LGTM!

Also applies to: 234-257, 309-363

src/aiq_agent/agents/deep_researcher/prompts/writer.j2 (1)

6-17: LGTM!

Also applies to: 60-73, 97-126, 147-148

src/aiq_agent/agents/deep_researcher/register.py (2)

113-128: LGTM!


254-255: LGTM!

Also applies to: 291-292

src/aiq_agent/agents/deep_researcher/tools/research.py (3)

51-98: LGTM!


144-178: LGTM!


212-263: LGTM!

Also applies to: 337-345

tests/aiq_agent/agents/deep_researcher/tools/test_research.py (1)

90-345: LGTM!

tests/aiq_agent/agents/deep_researcher/test_agent.py (1)

286-370: LGTM!

Also applies to: 518-532, 593-606, 1573-1882

frontends/aiq_api/src/aiq_api/jobs/runner.py (4)

48-69: LGTM!

Also applies to: 265-283


303-377: LGTM!


936-964: LGTM!

Also applies to: 990-994, 1034-1123


1184-1371: LGTM!

Also applies to: 1557-1577

tests/aiq_agent/jobs/test_runner.py (1)

1594-2235: LGTM!

Also applies to: 2639-2698, 3092-3093, 3226-3227

docs/source/customization/configuration-reference.md (1)

471-510: LGTM!

docs/source/examples/skills-sandbox/index.md (1)

9-256: LGTM!

src/aiq_agent/agents/deep_researcher/skills/research/data-table-analysis/SKILL.md (1)

9-240: LGTM! The previously-flagged missing aggregate CSV limit documentation is now present (line 51).

src/aiq_agent/agents/deep_researcher/skills/synthesis/chart-generation/SKILL.md (1)

1-181: LGTM!

src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py (2)

202-324: LGTM! Digest computation, freezing, and CSV structural validation are consistent with the trust-boundary requirements described in custom_middleware.py/research.py.


383-419: LGTM!

src/aiq_agent/agents/deep_researcher/models/__init__.py (1)

21-38: LGTM!

src/aiq_agent/agents/deep_researcher/prompts/orchestrator.j2 (1)

18-20: LGTM! Consistent with the writer-exclusive sandbox_artifact_dir ownership contract enforced in researcher.j2 and the SKILL.md files.

Also applies to: 137-137

src/aiq_agent/agents/deep_researcher/prompts/researcher.j2 (1)

46-57: LGTM!

Also applies to: 82-83

tests/aiq_agent/agents/deep_researcher/models/test_subagent_contracts.py (1)

18-92: LGTM! Boundary cases (exact 64KiB/128KiB, exact row/column limits, CRLF/Unicode CSV, quote-escaping) are well covered and match the validator constants.

Also applies to: 239-387

src/aiq_agent/agents/deep_researcher/custom_middleware.py (2)

1422-1441: CRLF normalization is now applied to both baseline and each markdown_table here (matching _baseline_contains_tables), which addresses the earlier nit.


65-253: LGTM!

Also applies to: 1030-1262, 1264-1561

src/aiq_agent/agents/deep_researcher/sandbox/artifacts/__init__.py (1)

24-24: LGTM!

Also applies to: 53-53

src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manifest.py (1)

46-49: LGTM!

Also applies to: 66-79

src/aiq_agent/agents/deep_researcher/sandbox/artifacts/manager.py (2)

208-276: LGTM!

Also applies to: 449-514, 611-755


72-74: 🩺 Stability & Availability

Confirm Modal’s missing-file error literal before relying on exact-match fallback. OpenShell maps absent files to file_not_found, but Modal’s download_files error shape still needs a check; if it uses a different token, manifest-less jobs will fail closed instead of scanning.

CHANGELOG.md (1)

26-27: LGTM!

README.md (1)

69-69: LGTM!

Also applies to: 288-288, 464-466

tests/aiq_agent/fastapi_extensions/test_deep_research.py (1)

55-60: LGTM!

Also applies to: 276-313, 323-381

tests/aiq_agent/agents/deep_researcher/test_custom_middleware.py (1)

1162-1236: LGTM!

Also applies to: 1265-1341, 1374-1488, 1490-2034

tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py (1)

58-58: LGTM!

Also applies to: 67-71, 248-300, 318-424, 426-520, 521-631, 632-683

tests/aiq_agent/agents/deep_researcher/test_artifact_publication_regression.py (1)

56-134: LGTM!

Also applies to: 173-341, 344-380

src/aiq_agent/agents/deep_researcher/factory.py (3)

147-147: LGTM!

Also applies to: 283-315, 485-488, 508-508, 561-561, 594-594, 605-610, 623-623, 652-652


424-431: LGTM!


374-406: 🩺 Stability & Availability

ModelRetryMiddleware.retry_on already accepts callables. The copied middleware can use _exclude_structured_output_from_model_retries(...) as-is; there’s no isinstance mismatch here.

			> Likely an incorrect or invalid review comment.
src/aiq_agent/agents/deep_researcher/agent.py (3)

40-69: LGTM!

Also applies to: 94-127, 219-219


229-248: LGTM!


250-466: LGTM!

tests/aiq_agent/agents/deep_researcher/test_factory.py (1)

204-287: LGTM!

Also applies to: 324-333, 455-471, 529-566

docs/source/architecture/agents/sandbox.md (1)

66-72: LGTM!

docs/source/architecture/data-flow.md (1)

44-44: LGTM!

Also applies to: 63-69, 187-191

docs/source/deployment/openshell.md (1)

407-408: LGTM!

Also applies to: 423-428

docs/source/integration/rest-api.md (1)

307-312: LGTM!

tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py (1)

254-263: LGTM!

tests/aiq_agent/agents/deep_researcher/test_chart_artifact_contract.py (1)

4-101: LGTM!

Also applies to: 103-167

Comment on lines +332 to +366
def _validate_csv_quote_placement(csv_text: str) -> None:
"""Reject quote placement that ``csv.reader(strict=True)`` accepts leniently.

The standard-library parser treats a quote inside an unquoted field as a
literal character even in strict mode. Such input is not portable CSV and
downstream readers may interpret it differently. This state check inspects
the original text without normalizing or reserializing it.
"""
field_start = "field_start"
unquoted = "unquoted"
quoted = "quoted"
after_quote = "after_quote"
state = field_start

for character in csv_text:
if state == field_start:
if character == '"':
state = quoted
elif character not in {",", "\r", "\n"}:
state = unquoted
elif state == unquoted:
if character == '"':
raise ValueError("csv_text contains malformed quote placement")
if character in {",", "\r", "\n"}:
state = field_start
elif state == quoted:
if character == '"':
state = after_quote
elif character == '"':
state = quoted
elif character in {",", "\r", "\n"}:
state = field_start
else:
raise ValueError("csv_text contains malformed quote placement")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Quote-placement state machine implicitly relies on state elimination for after_quote.

The after_quote state is never checked by name (state == "after_quote"); its three transitions are reached only because the preceding elif state == quoted etc. have already been ruled out. I traced this against all current test cases and it's correct, but the implicit "whatever's left must be after_quote" style is easy to break silently if a 5th state or new field-start branch is ever added.

♻️ Suggested explicit state check
-        elif state == quoted:
-            if character == '"':
-                state = after_quote
-        elif character == '"':
-            state = quoted
-        elif character in {",", "\r", "\n"}:
-            state = field_start
-        else:
-            raise ValueError("csv_text contains malformed quote placement")
+        elif state == quoted:
+            if character == '"':
+                state = after_quote
+        elif state == after_quote:
+            if character == '"':
+                state = quoted
+            elif character in {",", "\r", "\n"}:
+                state = field_start
+            else:
+                raise ValueError("csv_text contains malformed quote placement")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _validate_csv_quote_placement(csv_text: str) -> None:
"""Reject quote placement that ``csv.reader(strict=True)`` accepts leniently.
The standard-library parser treats a quote inside an unquoted field as a
literal character even in strict mode. Such input is not portable CSV and
downstream readers may interpret it differently. This state check inspects
the original text without normalizing or reserializing it.
"""
field_start = "field_start"
unquoted = "unquoted"
quoted = "quoted"
after_quote = "after_quote"
state = field_start
for character in csv_text:
if state == field_start:
if character == '"':
state = quoted
elif character not in {",", "\r", "\n"}:
state = unquoted
elif state == unquoted:
if character == '"':
raise ValueError("csv_text contains malformed quote placement")
if character in {",", "\r", "\n"}:
state = field_start
elif state == quoted:
if character == '"':
state = after_quote
elif character == '"':
state = quoted
elif character in {",", "\r", "\n"}:
state = field_start
else:
raise ValueError("csv_text contains malformed quote placement")
def _validate_csv_quote_placement(csv_text: str) -> None:
"""Reject quote placement that ``csv.reader(strict=True)`` accepts leniently.
The standard-library parser treats a quote inside an unquoted field as a
literal character even in strict mode. Such input is not portable CSV and
downstream readers may interpret it differently. This state check inspects
the original text without normalizing or reserializing it.
"""
field_start = "field_start"
unquoted = "unquoted"
quoted = "quoted"
after_quote = "after_quote"
state = field_start
for character in csv_text:
if state == field_start:
if character == '"':
state = quoted
elif character not in {",", "\r", "\n"}:
state = unquoted
elif state == unquoted:
if character == '"':
raise ValueError("csv_text contains malformed quote placement")
if character in {",", "\r", "\n"}:
state = field_start
elif state == quoted:
if character == '"':
state = after_quote
elif state == after_quote:
if character == '"':
state = quoted
elif character in {",", "\r", "\n"}:
state = field_start
else:
raise ValueError("csv_text contains malformed quote placement")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiq_agent/agents/deep_researcher/models/subagent_contracts.py` around
lines 332 - 366, Update _validate_csv_quote_placement to handle the after_quote
state with an explicit state == after_quote branch before its transitions.
Preserve the existing behavior: a following quote returns to quoted, a delimiter
or line break starts a new field, and any other character raises ValueError.

Comment on lines +174 to +178
- Writer manifest writes and successful writer chart executions trigger a manifest-only
checkpoint. Terminal finalization runs one manifest + directory scan on success or failure.
On cancellation, that scan runs only when the provider operation lease is immediately
available; for a busy sandbox the runtime requests immediate termination without extending
the worker's bounded wait, while completed writer outputs remain preserved by earlier checkpoints.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clarify the terminal-scan exception for cancellation.

“Terminal finalization runs one manifest + directory scan on success or failure” includes cancellation, but the following sentence says a busy sandbox skips that scan. State explicitly that the scan applies to non-cancelled completion, or that cancellation is an exception.

As per path instructions, agent changes must be reviewed for workflow correctness and failure handling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiq_agent/agents/deep_researcher/sandbox/README.md` around lines 174 -
178, Clarify the checkpointing paragraph in the README so “terminal
finalization” explicitly applies to non-cancelled success or failure, while
cancellation is an exception: a busy sandbox may skip the manifest and directory
scan and request immediate termination. Preserve the existing behavior for an
immediately available provider operation lease and retained writer outputs.

Source: Path instructions

Comment on lines +314 to +322
def download_files(self, paths):
return [
SimpleNamespace(
path=path,
content=self.files.get(path),
error=None if path in self.files else "not found",
)
for path in paths
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align this stub's error token with the structured "file_not_found" signal. manager._manifest_error_is_not_found matches that exact token, and the sibling doubles (test_artifacts.py, test_artifact_publication_regression.py) already use it. "not found" only passes here because every path is present; as a reusable pattern it silently misclassifies genuine absence as unreadable/fail-closed.

Suggested change
-                        error=None if path in self.files else "not found",
+                        error=None if path in self.files else "file_not_found",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def download_files(self, paths):
return [
SimpleNamespace(
path=path,
content=self.files.get(path),
error=None if path in self.files else "not found",
)
for path in paths
]
def download_files(self, paths):
return [
SimpleNamespace(
path=path,
content=self.files.get(path),
error=None if path in self.files else "file_not_found",
)
for path in paths
]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/aiq_agent/fastapi_extensions/test_deep_research.py` around lines 314 -
322, Update the download_files stub’s missing-file error value to the structured
"file_not_found" token expected by manager._manifest_error_is_not_found,
matching the sibling test doubles. Keep successful file entries unchanged while
ensuring absent paths produce the exact not-found signal.

@KyleZheng1284

Copy link
Copy Markdown
Contributor Author

/ok to test 7f72f47

Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
@KyleZheng1284
KyleZheng1284 force-pushed the fix/deep-research-convergence branch from 175f4fc to f3d9c85 Compare July 23, 2026 21:49
rapids-bot Bot pushed a commit that referenced this pull request Jul 24, 2026
#### Overview

Document AI-Q's actual OpenShell execution boundary for architecture and security review, and present OpenShell before Modal in the deep-research sandbox implementation reference.

This documentation-only change now:

- makes the shared AI-Q provider lifecycle explicit: job scope, lazy single-flight creation, serialized operations, idempotency-gated retry, artifact harvesting, and terminal cleanup apply to both OpenShell and Modal;
- documents the complete agent-visible filesystem/code surface (`ls`, `glob`, `grep`, `read_file`, `write_file`, `edit_file`, and `execute`) and distinguishes provider-backed paths from host-side `/shared/` and `/skills/` routes;
- distinguishes public `deep_research_sandbox` workflow YAML from the internal normalized `SandboxConfig`, and links the validated `configs/config_openshell.yml` public profile;
- defines the distinct AI-Q API principal, asynchronous job, job-scoped OpenShell sandbox, and external tenant boundaries;
- documents the current async submission order, including the race between Dask worker startup and `job_access` persistence;
- separates the architecture into two rendered sequence diagrams: submission/provisioning/policy verification and execution/artifacts/teardown;
- shows the real OpenShell data and control path: AI-Q runtime and SDK -> authenticated Gateway -> sandbox Supervisor -> restricted child process;
- describes the local policy preflight and the exact Gateway phase, source, protobuf, hash, and version agreement required before AI-Q constructs the OpenShell adapter;
- distinguishes fail-closed, point-in-time control-plane policy verification from hardware-backed remote attestation or independent kernel measurement;
- documents that end-user authorization remains in AI-Q and that the Gateway authenticates the configured AI-Q service/operator connection, not the API caller;
- describes conditional artifact checkpointing, terminal harvesting, EventStore persistence, and the host-side `/shared/` and `/skills/` routes;
- states the handled cleanup guarantees and the process-loss limit: SDK context exit requests deletion, but the AI-Q ghost-job reaper does not delete OpenShell resources, so production orphan reconciliation remains external;
- qualifies labels as discovery metadata rather than authorization or uniqueness boundaries; and
- consistently presents OpenShell before Modal in the isolation overview, module map, configuration example, and provider sections.

The reference also corrects two adjacent artifact statements: `/skills/` uses a host-side filesystem backend rather than `StateBackend`, and SVG is currently rejected until a vetted sanitizer exists rather than sanitized or stored as download-only content.

No runtime, configuration, policy, API, or dependency behavior changes.

#### DCO sign-off for the squash commit

Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Signed-off-by: Kyle Zheng <126034466+KyleZheng1284@users.noreply.github.com>

#### Validation

- [x] `.venv/bin/pytest -q tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py tests/aiq_agent/agents/deep_researcher/sandbox/test_sandbox_runtime.py tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py tests/aiq_agent/jobs/test_runner.py frontends/aiq_api/tests/test_job_access.py frontends/aiq_api/tests/test_submit_collision.py frontends/aiq_api/tests/test_submit_owner_user_id.py` — 328 passed, 2 skipped.
- [x] `.venv/bin/pytest -q tests/aiq_agent/agents/deep_researcher/sandbox/test_artifacts.py tests/aiq_agent/agents/deep_researcher/sandbox/test_sandbox_runtime.py tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py` — 114 passed after the review-fix commit.
- [x] `.venv/bin/pytest -q tests/aiq_agent/agents/deep_researcher/test_factory.py::test_middleware_set_adds_orchestrator_batch_tool_name` — 1 passed after the filesystem-tool documentation correction.
- [x] `.venv/bin/python .agents/skills/aiq-configure-workflow/scripts/validate_config.py configs/config_openshell.yml` — no errors and 0 warnings.
- [x] Schema probe — the README example validates as internal `SandboxConfig`, the linked OpenShell block validates as public `DeepResearchSandboxConfig`, and the internal shape is rejected by the public schema.
- [x] `.venv/bin/pre-commit run --files src/aiq_agent/agents/deep_researcher/sandbox/README.md` — all applicable hooks passed, including merge-conflict, large-file, whitespace, secret, and network-backed Markdown link checks.
- [x] Extracted both `mermaid` blocks and rendered each with `npx --yes @mermaid-js/mermaid-cli` — both SVGs generated without parse errors.
- [x] `git diff --check origin/release/2.2..HEAD` — passed.
- [x] Verified the rebased branch contains five DCO-signed commits and changes only `src/aiq_agent/agents/deep_researcher/sandbox/README.md`.
- [x] I ran the relevant local checks or explained why they are not applicable.
- [x] I added or updated tests for behavior changes. No tests were added because this PR changes documentation only; the focused existing suites above validate the described behavior.
- [x] I updated documentation for user-facing or contributor-facing changes.
- [x] I confirmed this PR does not include secrets, credentials, or internal-only data.
- [x] I certify this contribution under the Developer Certificate of Origin (DCO) and signed my commits with `git commit -s` or an equivalent sign-off.
- [x] I replaced the DCO sign-off placeholder with my GitHub commit identity and kept the required angle brackets around the email address.

#### Where should reviewers start?

Start in `src/aiq_agent/agents/deep_researcher/sandbox/README.md` at `Providers`:

1. `Workspace organization and isolation limits` for the complete filesystem/code tool set and provider-backed versus host-side routing.
2. `Shared AI-Q provider lifecycle` for the job scope, lazy creation, serialization/retry, artifact, and terminal contracts common to OpenShell and Modal.
3. `Internal normalized SandboxConfig` for the explicit boundary between public workflow YAML and the runtime's internal provider model.
4. `OpenShell > Boundary model` for the principal/job/sandbox/tenant definitions and identity split.
5. `OpenShell > Submission, provisioning, and policy verification` for the actual Dask and Gateway/Supervisor ordering.
6. `OpenShell > OpenShell transport during execution and teardown` for the provider relay around the shared artifact and terminal lifecycle.
7. `OpenShell > What AI-Q policy verification proves` for the exact assurance boundary and point-in-time limitation.
8. `OpenShell > Lifecycle guarantees and reconciliation limits` for OpenShell deletion requests, labels, and orphan handling.
9. `Modal (cloud)` for its provider-specific creation, controls, retry classification, and teardown adapter.

The main review question is whether the shared AI-Q lifecycle is clearly separated from provider-specific behavior, and whether the OpenShell sections describe the implementation precisely without overstating end-user identity propagation, continuous policy attestation, or deletion guarantees.

#### Related Issues

- Relates to #373; the documentation remains split into this PR to keep the artifact-convergence implementation review scoped.



## Summary by CodeRabbit

* **Documentation**
  * Clarified the Deep Research Sandbox security boundary, keeping host orchestration state separate from provider execution and restricting what sandbox creation requests can include.
  * Updated guidance on workspace isolation, scoped reattachment, and fail-closed attestation/policy verification.
  * Strengthened instructions for artifact harvesting and report post-processing, including manifest-first validation, safer reference rewriting, and `artifact://<id>` handling.
  * Refreshed operational/troubleshooting notes for OpenShell and Modal/cloud, including sandbox lifecycle ordering and single-retry semantics for idempotent transfers.

Authors:
  - Kyle Zheng (https://github.com/KyleZheng1284)

Approvers:
  - Soumili Nandi (https://github.com/soumilinandi)

URL: #387
@coderabbitai coderabbitai Bot mentioned this pull request Aug 4, 2026
13 tasks done
@AjayThorve AjayThorve removed this from the v2.2 milestone Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants