Skip to content

feat: isolate and attest OpenShell jobs#298

Open
KyleZheng1284 wants to merge 20 commits into
NVIDIA-AI-Blueprints:developfrom
KyleZheng1284:codex/aiq-openshell-isolation
Open

feat: isolate and attest OpenShell jobs#298
KyleZheng1284 wants to merge 20 commits into
NVIDIA-AI-Blueprints:developfrom
KyleZheng1284:codex/aiq-openshell-isolation

Conversation

@KyleZheng1284

@KyleZheng1284 KyleZheng1284 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Overview

Create and own one physical OpenShell sandbox per AI-Q deep-research job. This change provides per-job isolation, fail-closed policy attestation, discoverable ownership labels, truthful cleanup, authenticated gateway readiness checks, pytest-owned live acceptance, and a canonical operator guide.

The previous upstream blocker is resolved by NVIDIA/OpenShell#2170 and released in OpenShell 0.0.80. AI-Q now requires openshell>=0.0.80,<0.1; the setup script defaults to and enforces the 0.0.80 supported floor. Runtime security decisions remain capability- and state-based rather than branching on an OpenShell version.

Key behavior:

  • Create one owned physical sandbox lazily for each job and reuse it for subsequent execution within that job.
  • Apply aiq=deep-research and a normalized aiq-job-id to both OpenShell request metadata and template/container metadata. Active jobs can be queried with openshell sandbox list --selector aiq=deep-research.
  • Keep shared-sandbox attachment behind explicit allow_shared_sandbox=true debug configuration. Shared attachment is non-production and does not transfer cleanup ownership to the job.
  • Enforce AI-Q's declared network boundary:
    • blocked mode rejects every configured endpoint;
    • allowlist mode requires non-empty normalized hosts contained in network.allow;
    • hostless endpoints and allowed_ips/CIDR exceptions are rejected.
  • Attest the exact submitted policy through authoritative OpenShell policy-status and effective-config RPCs before exposing the execution adapter.
  • Require READY, a LOADED revision without a load error, sandbox policy source, matching positive current/active/revision/config versions, structurally identical policies, and matching deterministic hashes.
  • Treat a matching effective policy that remains PENDING as policy_status_inconsistent; never weaken attestation because the sandbox is otherwise executable.
  • Use the canonical read-only /proc baseline so OpenShell does not enrich the submitted policy and create an unexpected revision.
  • Delete and verify deletion of partially created sandboxes after attestation, startup, execution, timeout, failure, or cancellation errors.
  • Emit structured, sanitized sandbox.attestation and sandbox.cleanup events without exception messages, policy contents, credentials, SDK response bodies, or tracebacks.
  • Preserve artifact harvest-before-cleanup ordering from develop/PR feat: persist and surface sandbox artifacts #314. Generated files remain job-scoped, receive durable artifact references, and can be rendered or downloaded after the physical sandbox is deleted.
  • Add one writer-local corrective turn when the writer claims Wrote /shared/output.md without creating a non-empty output file. A second false completion fails with the stable writer_output_missing reason instead of restarting research.
  • Split provisioning from gateway lifecycle:
    • setup_openshell.sh installs the pinned dependencies, generates policy, and builds the image;
    • start_openshell_gateway.sh validates an authenticated registered or packaged gateway and performs a disposable strict readiness probe;
    • AI-Q never launches a raw gateway binary or broadly kills externally owned processes.
  • Make the readiness probe verify CLI/SDK/gateway version agreement, request labels, selector behavior, READY, LOADED, effective policy source/content/hash/version, command execution, deletion, and confirmed absence.
  • Move live assertions, resources, and verified teardown into pytest. scripts/smoke_openshell_isolation.py is now only a thin compatibility launcher.
  • Add docs/source/deployment/openshell.md as the canonical operator guide for platform support, ownership, policy/config pairing, startup, acceptance, and troubleshooting.

Reviewer Test Instructions

On macOS with Docker Desktop, this is a functional local-demo flow using explicit Landlock best_effort:

gh pr checkout 298
./scripts/setup.sh
source .venv/bin/activate

/opt/homebrew/bin/bash ./scripts/setup_openshell.sh \
  --openshell-version 0.0.80 \
  --local-demo \
  --policy offline

export AIQ_OPENSHELL_GATEWAY_NAME=openshell
export AIQ_OPENSHELL_IMAGE=aiq-openshell-demo:latest
export AIQ_OPENSHELL_POLICY_FILE="$PWD/configs/openshell/generated/aiq-openshell-policy.yaml"
export AIQ_OPENSHELL_EXPECTED_GATEWAY_VERSION=0.0.80
export AIQ_OPENSHELL_REQUIRE_HARD_LANDLOCK=false

nat validate --config_file configs/config_openshell.yml

./scripts/start_openshell_gateway.sh \
  --gateway-name openshell \
  --image-name aiq-openshell-demo:latest \
  --policy-file configs/openshell/generated/aiq-openshell-policy.yaml

./scripts/start_e2e.sh \
  --config_file configs/config_openshell.yml

Open http://localhost:3000, create two sessions, and start two concurrent deep-research jobs that request a CSV and PNG chart.

While both jobs are running:

.venv/bin/openshell sandbox list \
  -g openshell \
  --selector aiq=deep-research \
  -o json

Expected behavior:

  1. Each job creates one distinct physical sandbox when sandbox execution is first needed.
  2. Later execution calls from the same job reuse that sandbox.
  3. Both sandboxes have aiq=deep-research and distinct aiq-job-id labels.
  4. Attestation succeeds before sandbox execution is exposed to the agent.
  5. Generated files appear under the correct job and remain downloadable after cleanup.
  6. PNG artifacts render through durable artifact references in the final report.
  7. A false writer completion receives one writer-local retry.
  8. Cancelling one job deletes only that job's sandbox; the other job remains usable.
  9. Completing the remaining job deletes its sandbox.
  10. The selector returns no matching sandboxes after both jobs terminate.

Run the automated macOS live suite with:

.venv/bin/python scripts/smoke_openshell_isolation.py \
  --gateway openshell \
  --policy configs/openshell/generated/aiq-openshell-policy.yaml \
  --image aiq-openshell-demo:latest \
  --expected-gateway-version 0.0.80 \
  --allow-best-effort-landlock

Expected result: all three live isolation/attestation, failure-cleanup/redaction, and shared-policy-mismatch tests pass.

A passing macOS run is functional demo evidence only. Production acceptance requires Linux, Docker, and a policy/config pairing that both require Landlock hard_requirement, as documented in docs/source/deployment/openshell.md.

Validation

Current-head scoped regression suite:

.venv/bin/pytest -q \
  tests/aiq_agent/agents/deep_researcher \
  tests/aiq_agent/jobs/test_runner.py \
  tests/scripts/test_openshell_lifecycle_scripts.py \
  tests/scripts/test_openshell_readiness_checker.py \
  tests/scripts/test_openshell_smoke_wrapper.py

Result: 485 passed, 3 skipped in 6.58s. The three live tests were collected and skipped before optional OpenShell imports or gateway access because live testing was not enabled.

Lint, formatting, and shell syntax:

.venv/bin/ruff check \
  src/aiq_agent/agents/deep_researcher \
  tests/aiq_agent/agents/deep_researcher \
  tests/aiq_agent/jobs/test_runner.py \
  tests/scripts \
  scripts/check_openshell_readiness.py \
  scripts/smoke_openshell_isolation.py

.venv/bin/ruff format --check \
  src/aiq_agent/agents/deep_researcher \
  tests/aiq_agent/agents/deep_researcher \
  tests/aiq_agent/jobs/test_runner.py \
  tests/scripts \
  scripts/check_openshell_readiness.py \
  scripts/smoke_openshell_isolation.py

bash -n \
  scripts/setup_openshell.sh \
  scripts/start_openshell_gateway.sh \
  scripts/start_e2e.sh

Result: all checks passed; 53 files already formatted; shell syntax passed.

Configuration validation:

AIQ_OPENSHELL_REQUIRE_HARD_LANDLOCK=false \
  .venv/bin/nat validate \
  --config_file configs/config_openshell.yml

Result: configuration valid.

Documentation:

cd docs
PATH="../.venv/bin:$PATH" make html

Result: build succeeded. Sphinx reported two existing unresolved links in docs/source/integration/agent-skills.md; neither warning originates from the OpenShell documentation.

Live OpenShell validation recorded on macOS/Docker Desktop after the 0.0.80 rebaseline:

  • 3 live tests passed.
  • Proved concurrent per-job isolation and selector visibility.
  • Proved source/content/hash/revision attestation.
  • Proved isolated cancellation and verified terminal deletion.
  • Proved failure cleanup and credential-canary redaction.
  • Proved shared-policy mismatch rejection.

Linux/Docker acceptance with Landlock hard_requirement has not been run on this macOS host and remains the production acceptance 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.

Where should reviewers start?

  1. src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py

    • Per-job creation, labels, network validation, authoritative attestation, and cleanup.
  2. scripts/check_openshell_readiness.py and scripts/start_openshell_gateway.sh

    • Version/capability validation, selector proof, policy verification, execution, and verified deletion.
  3. tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_live.py

    • Pytest-owned live isolation, cancellation, cleanup/redaction, and shared-policy rejection.
  4. src/aiq_agent/agents/deep_researcher/custom_middleware.py

    • Deterministic filesystem arguments, sandbox-path validation, and the bounded writer-output guard.
  5. docs/source/deployment/openshell.md

    • Canonical supported-platform, ownership, provisioning, startup, acceptance, and troubleshooting contract.

Related Issues

@copy-pr-bot

copy-pr-bot Bot commented Jul 1, 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.

@coderabbitai

coderabbitai Bot commented Jul 1, 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

OpenShell deep-research sandboxing now uses per-job policy-bound sandboxes with attestation and allowlist networking. Cleanup now flows through finalize() from the agent and runtime into job teardown, with updated setup scripts, docs, smoke tests, and validation coverage.

Changes

Per-job OpenShell sandbox and finalize lifecycle

Layer / File(s) Summary
Sandbox config contracts
src/aiq_agent/agents/deep_researcher/sandbox/config.py, src/aiq_agent/agents/deep_researcher/deepagents_runtime.py, configs/config_openshell.yml, configs/openshell/aiq-research-policy.yaml
Adds per-job OpenShell image, shared-attachment, attestation, Landlock, and allowlist-network fields, with matching config and policy defaults.
OpenShell provider and runtime cleanup
src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py, src/aiq_agent/agents/deep_researcher/sandbox/base.py, src/aiq_agent/agents/deep_researcher/deepagents_runtime.py, src/aiq_agent/agents/deep_researcher/agent.py, src/aiq_agent/agents/deep_researcher/register.py, frontends/aiq_api/src/aiq_api/jobs/runner.py
Adds strict policy parsing, per-job or debug attachment handling, attestation checks, cleanup telemetry, and finalize-based teardown through the provider, runtime, agent, and job runner.
Setup script, smoke test, and docs
scripts/setup_openshell.sh, scripts/smoke_openshell_isolation.py, scripts/README.md, docs/source/architecture/agents/sandbox.md, src/aiq_agent/agents/deep_researcher/sandbox/README.md
Updates OpenShell setup behavior, generated policy handling, live isolation smoke testing, and workflow documentation for per-job sandboxes and Landlock modes.
Provider, runtime, agent, and runner tests
tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py, tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py, tests/aiq_agent/agents/deep_researcher/test_agent.py, tests/aiq_agent/jobs/test_runner.py
Expands coverage for policy validation, attestation behavior, finalize idempotency and concurrency, cancellation cleanup, and teardown preference.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • NVIDIA-AI-Blueprints/aiq#280: Updates the same deep-research sandbox/runtime and provider lifecycle path that this PR extends with per-job OpenShell cleanup.
  • NVIDIA-AI-Blueprints/aiq#284: Touches the same deep_researcher config and runtime wiring that this PR builds on for OpenShell sandbox behavior.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title is Conventional Commits compliant and accurately summarizes the main OpenShell isolation/attestation change.
Description check ✅ Passed The description follows the template with Overview, Validation, reviewer start points, and related issues, and it includes concrete checks and commands.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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: 6

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/sandbox/base.py (1)

204-216: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

_cleanup_failed is sticky across mid-life retries, causing false-negative terminal cleanup reports.

_safe_close (and its cousin in openshell.py's _exit_context) sets self._cleanup_failed = True on any close exception, but _safe_close is also invoked from _reset_session() when tearing down a stale session before recreating it on a recoverable error — a mid-job event unrelated to terminal cleanup. Since _cleanup_failed is never reset, a transient failure during that retry path permanently poisons cleanup_succeeded, so finalize() will report "failed" for a job whose actual terminal close() succeeded without error. This undermines the truthful-cleanup-reporting goal called out in the PR description.

Consider tracking retry-teardown failures separately from terminal-cleanup failures, or resetting _cleanup_failed at the start of _reset_session() since a fresh session is about to replace the stale one.

🤖 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/base.py` around lines 204 - 216,
The cleanup failure flag in `_safe_close` is being left sticky across session
retries, so a stale-session close error can incorrectly make `cleanup_succeeded`
fail later. Update `BaseSandbox._safe_close` and the retry path in
`_reset_session()` to distinguish mid-life teardown from final terminal cleanup,
or reset `_cleanup_failed` when starting a fresh session replacement. Keep the
terminal reporting used by `cleanup_succeeded`/`finalize()` tied only to the
actual final close, and apply the same handling to the corresponding
`_exit_context` logic in `openshell.py` if it shares the same flag.
🤖 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 `@frontends/aiq_api/src/aiq_api/jobs/runner.py`:
- Around line 667-673: In runner.py, the sandbox cleanup path around finalize()
only logs when an exception is raised, so a falsy cleanup result is missed.
Update the finalize handling in the job runner to inspect the return value from
sandbox_runtime.finalize(...) and emit the same warning path when it returns
False or another falsy cleanup_succeeded value, while still keeping exception
handling non-fatal. Use the existing finalize, job_id, and logger.warning call
site to keep the behavior aligned with the current cleanup flow.

In `@src/aiq_agent/agents/deep_researcher/deepagents_runtime.py`:
- Around line 278-298: The finalize() idempotency guard in DeepResearcherRuntime
only protects the _finalized flag, so concurrent calls can skip the actual
cleanup and return a stale success from cleanup_succeeded. Update finalize() to
keep the same lock across the full cleanup path, including terminate()/close(),
the cleanup result read, and the cleanup event emission, so only one caller
performs cleanup and others wait for the real outcome. Use the existing
finalize(), _finalize_lock, _finalized, _emit_cleanup, terminate(), and close()
symbols to make the change.

In `@src/aiq_agent/agents/deep_researcher/register.py`:
- Around line 298-306: Add a unit test in test_agent.py that covers _run
cancellation behavior in DeepResearcher register.py: simulate
asyncio.CancelledError during _run, assert the exception is re-raised, and
verify active_agent.finalize is called with interrupted=True only when
owns_active_agent is true. Use the _run coroutine and the owns_active_agent /
active_agent.finalize path to confirm no finalization happens for non-owned
agents and that the finally block triggers the correct interrupted flag for
owned agents.

In `@src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py`:
- Around line 368-423: `_os_context` is being accessed concurrently between
`_create_session()` and out-of-band cleanup paths like `close()`, `terminate()`,
and `_terminate_session()`, which can race during cancellation. Protect all
reads/writes/clears of `self._os_context` with the existing `self._state_lock`,
matching how `self._session` is guarded in the base class, and ensure
`_exit_context()` and the `_create_session()` setup/teardown path in
`OpenShellSandboxProvider` use the same lock consistently.
- Around line 406-424: The attestation success event is emitted too early in
OpenShellSandbox creation, before session construction is guaranteed to succeed.
In openshell.py, adjust the _create_session flow so _attest(self, os_sandbox)
and its “succeeded” emission happen only after OpenShellSandbox(...) is
successfully constructed, or add a failure compensation path if adapter
construction throws. Keep the logging and cleanup around os_sandbox.__enter__
and _exit_context() unchanged, but ensure the attestation state reflects the
final outcome of _create_session rather than the pre-construction step.

In `@tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py`:
- Around line 151-441: Add a regression test around OpenShellSandboxProvider
that exercises _reset_session() after a stale-session close failure, then
performs a later successful terminal close and verifies cleanup_succeeded
remains True. Use the existing _reset_session and close behavior in the
provider/test setup to force the first cleanup failure, then assert the second
close does not stay poisoned by _cleanup_failed.

---

Outside diff comments:
In `@src/aiq_agent/agents/deep_researcher/sandbox/base.py`:
- Around line 204-216: The cleanup failure flag in `_safe_close` is being left
sticky across session retries, so a stale-session close error can incorrectly
make `cleanup_succeeded` fail later. Update `BaseSandbox._safe_close` and the
retry path in `_reset_session()` to distinguish mid-life teardown from final
terminal cleanup, or reset `_cleanup_failed` when starting a fresh session
replacement. Keep the terminal reporting used by
`cleanup_succeeded`/`finalize()` tied only to the actual final close, and apply
the same handling to the corresponding `_exit_context` logic in `openshell.py`
if it shares the same flag.
🪄 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: 63446849-608f-4056-8b4b-58895c90df18

📥 Commits

Reviewing files that changed from the base of the PR and between 09da539 and a3364f0.

📒 Files selected for processing (16)
  • configs/config_openshell.yml
  • configs/openshell/aiq-research-policy.yaml
  • docs/source/architecture/agents/sandbox.md
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • scripts/README.md
  • scripts/setup_openshell.sh
  • src/aiq_agent/agents/deep_researcher/agent.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/sandbox/README.md
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • src/aiq_agent/agents/deep_researcher/sandbox/config.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/jobs/test_runner.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (10)
**

⚙️ CodeRabbit configuration file

**:

AI-Q Agent Guidance

Repository-global instructions for coding agents and for humans reviewing
agent-authored changes. These rules apply to every task in this repository.
Task-specific runbooks live in .agents/skills/ — load the
relevant skill before starting a workflow it covers.

Project overview

AI-Q is an NVIDIA AI Blueprint: an enterprise research agent built on the
NeMo Agent Toolkit (NAT). The deployed product is a research blueprint, not
a general skill runtime. New retrieval sources and tools are NAT functions;
agent behavior is driven by workflow YAML, Jinja2 prompts, and a data-source
registry — not by hard-coded logic.

Primary boundaries:

  • Backend Python package: src/aiq_agent/.
  • Data-source and tool packages: sources/ (each is its own package).
  • Frontends and tooling: frontends/ (web UI in frontends/ui/, eval harnesses
    in frontends/benchmarks/).
  • Configs, deployment, docs: configs/, deploy/, docs/.

Stay inside this repository. If your workspace also contains adjacent repos
(for example a sibling NeMo-Relay checkout), do not edit them as part of an AI-Q
change. Treat sources/* as independent packages: prefer the smallest change
scoped to the package you are touching.

Repository structure

Path Purpose
src/aiq_agent/ Backend agent, FastAPI extensions, auth, observability, knowledge
sources/ Data-source / tool packages (e.g. tavily_web_search, google_scholar_paper_search)
configs/ Workflow YAML configs (e.g. config_cli_default.yml)
frontends/ui/ Next.js / React / TypeScript / Tailwind / KUI web UI
frontends/benchmarks/ Eval harnesses: freshqa, deepsearch_qa, deepresearch_bench
deploy/ Docker Compose and Helm/Kubernetes assets; deploy/.env for secrets
docs/source/ ...

Files:

  • scripts/README.md
  • tests/aiq_agent/jobs/test_runner.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • configs/config_openshell.yml
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • src/aiq_agent/agents/deep_researcher/register.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • configs/openshell/aiq-research-policy.yaml
  • docs/source/architecture/agents/sandbox.md
  • src/aiq_agent/agents/deep_researcher/sandbox/config.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • scripts/setup_openshell.sh
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/README.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:

  • tests/aiq_agent/jobs/test_runner.py
  • src/aiq_agent/agents/deep_researcher/agent.py
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • src/aiq_agent/agents/deep_researcher/register.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/config.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
**/*test*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run pytest for all behavior changes in Python code

Files:

  • tests/aiq_agent/jobs/test_runner.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.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/agent.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/sandbox/config.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.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/agent.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • src/aiq_agent/agents/deep_researcher/register.py
  • src/aiq_agent/agents/deep_researcher/sandbox/config.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/README.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_openshell.yml
  • configs/openshell/aiq-research-policy.yaml
{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
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/**,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
**/*config*.py

📄 CodeRabbit inference engine (AGENTS.md)

Config schemas must inherit from FunctionBaseConfig and YAML _type names must come from the registered config class

Files:

  • src/aiq_agent/agents/deep_researcher/sandbox/config.py
🔇 Additional comments (20)
configs/config_openshell.yml (1)

1-2: LGTM!

Also applies to: 142-165

configs/openshell/aiq-research-policy.yaml (1)

24-29: LGTM!

scripts/setup_openshell.sh (2)

17-17: LGTM!

Also applies to: 51-51, 61-61, 80-80, 93-95, 108-108, 1010-1010, 1062-1062, 1075-1075


155-162: LGTM!

Validation of LANDLOCK_COMPATIBILITY runs in resolve_policy() before emit_policy_header() writes it into the now-unquoted heredoc, so only the two whitelisted literals ever reach the generated policy file — no injection risk from the quoting change.

Also applies to: 878-884, 898-898, 922-925

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

9-11: LGTM!

Description of per-job physical sandboxes, attestation, fail-closed network/Landlock behavior, and sandbox.attestation/sandbox.cleanup events is consistent with the config and setup-script changes in this cohort.

Also applies to: 24-30, 40-46, 58-58

scripts/README.md (1)

54-65: LGTM!

Matches the setup script's new default (CREATE_SANDBOX=false), --landlock-compatibility/--create-shared-debug-sandbox flags, and Landlock defaults.

Also applies to: 190-190

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

40-50: LGTM!

Fail-closed conditions, attestation/policy-revision requirements, debug shared-sandbox opt-in, and troubleshooting guidance are consistent with the per-job sandbox design described across the config, policy, and setup-script changes in this cohort.

Also applies to: 65-65, 160-165, 211-217, 238-254, 277-279, 287-302


122-152: 📐 Maintainability & Code Quality

The sandbox example matches the current schema. SandboxConfig exposes network: NetworkPolicy with mode/allow, and providers.openshell.image is a real field, so this example is consistent with the code.

			> Likely an incorrect or invalid review comment.
src/aiq_agent/agents/deep_researcher/sandbox/config.py (1)

93-152: LGTM!

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

22-22: LGTM!

Also applies to: 36-36, 90-127, 136-162, 191-193, 207-208, 495-504, 517-517

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

155-158: LGTM!

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

18-18: LGTM!

Also applies to: 240-245, 272-272

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

649-650: LGTM!

src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py (3)

130-196: LGTM!


199-262: LGTM!

Also applies to: 291-292, 359-359


231-243: 🎯 Functional Correctness

open is an explicit unrestricted mode, so _validate_policy_network only needs to enforce blocked and allowlist.

			> Likely an incorrect or invalid review comment.
src/aiq_agent/agents/deep_researcher/sandbox/base.py (1)

102-103: LGTM!

Also applies to: 145-157

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

16-40: LGTM!

Also applies to: 68-148, 564-578

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

35-35: LGTM!

Also applies to: 306-377

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

1911-1920: LGTM!

Comment thread frontends/aiq_api/src/aiq_api/jobs/runner.py
Comment thread src/aiq_agent/agents/deep_researcher/deepagents_runtime.py Outdated
Comment thread src/aiq_agent/agents/deep_researcher/register.py
Comment thread src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
Comment thread src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
@KyleZheng1284

Copy link
Copy Markdown
Contributor Author

/ok to test b6b058e

@KyleZheng1284

KyleZheng1284 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Final CodeRabbit remediation is in 8e2287b (single DCO-signed commit). It serializes and caches terminal finalization, hardens OpenShell context ownership across creation/termination races, delays attestation success until adapter usability, adds owned/non-owned NAT cancellation coverage, and retains cumulative fail-closed cleanup status. Follow-up findings are also addressed: shared-sandbox aliases must agree, cleanup warnings contain exception types only, gateway identifiers are excluded from attestation logs, shared attachments never delete unowned sandboxes, false cleanup results remain observable, event-store flush failures cannot mask job outcomes, and public shared attachment requires explicit debug opt-in. Validation on the exact commit: provider 35 passed; deterministic remediation suite 239 passed; full PR-scoped suite 293 passed; Ruff check/format passed; config and shell validation passed; OpenShell 0.0.72 live smoke passed distinct creation, cancellation isolation, continued peer usability, and terminal deletion.

@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/sandbox/providers/openshell.py (1)

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

Do not delete attached debug sandboxes by default.

This shared-name branch attaches to an existing sandbox, but oscfg.delete_on_exit now defaults to True, so normal cleanup can delete a sandbox the job did not create. Keep deletion disabled for shared/debug attachment unless there is a separate explicit opt-in.

Suggested fix
-            sandbox_kwargs.update(sandbox=shared_name, delete_on_exit=oscfg.delete_on_exit)
+            sandbox_kwargs.update(sandbox=shared_name, delete_on_exit=False)
🤖 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/providers/openshell.py` at line
390, The shared-name attachment path in openshell.py is passing through
delete_on_exit from oscfg, which can accidentally delete an existing debug
sandbox that this job did not create. Update the sandbox_kwargs.update(...) call
in the shared-name branch to disable deletion by default for attached sandboxes,
and only allow cleanup when there is an explicit opt-in separate from the
shared/debug attach flow.
🤖 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/sandbox/providers/openshell.py`:
- Line 390: The shared-name attachment path in openshell.py is passing through
delete_on_exit from oscfg, which can accidentally delete an existing debug
sandbox that this job did not create. Update the sandbox_kwargs.update(...) call
in the shared-name branch to disable deletion by default for attached sandboxes,
and only allow cleanup when there is an explicit opt-in separate from the
shared/debug attach flow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 2ad4ae39-8901-4264-be74-600780d237d0

📥 Commits

Reviewing files that changed from the base of the PR and between b6b058e and e5b69ab.

📒 Files selected for processing (9)
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • scripts/smoke_openshell_isolation.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/jobs/test_runner.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.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:

  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/jobs/test_runner.py
  • scripts/smoke_openshell_isolation.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
**

⚙️ CodeRabbit configuration file

**:

AI-Q Agent Guidance

Repository-global instructions for coding agents and for humans reviewing
agent-authored changes. These rules apply to every task in this repository.
Task-specific runbooks live in .agents/skills/ — load the
relevant skill before starting a workflow it covers.

Project overview

AI-Q is an NVIDIA AI Blueprint: an enterprise research agent built on the
NeMo Agent Toolkit (NAT). The deployed product is a research blueprint, not
a general skill runtime. New retrieval sources and tools are NAT functions;
agent behavior is driven by workflow YAML, Jinja2 prompts, and a data-source
registry — not by hard-coded logic.

Primary boundaries:

  • Backend Python package: src/aiq_agent/.
  • Data-source and tool packages: sources/ (each is its own package).
  • Frontends and tooling: frontends/ (web UI in frontends/ui/, eval harnesses
    in frontends/benchmarks/).
  • Configs, deployment, docs: configs/, deploy/, docs/.

Stay inside this repository. If your workspace also contains adjacent repos
(for example a sibling NeMo-Relay checkout), do not edit them as part of an AI-Q
change. Treat sources/* as independent packages: prefer the smallest change
scoped to the package you are touching.

Repository structure

Path Purpose
src/aiq_agent/ Backend agent, FastAPI extensions, auth, observability, knowledge
sources/ Data-source / tool packages (e.g. tavily_web_search, google_scholar_paper_search)
configs/ Workflow YAML configs (e.g. config_cli_default.yml)
frontends/ui/ Next.js / React / TypeScript / Tailwind / KUI web UI
frontends/benchmarks/ Eval harnesses: freshqa, deepsearch_qa, deepresearch_bench
deploy/ Docker Compose and Helm/Kubernetes assets; deploy/.env for secrets
docs/source/ ...

Files:

  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/jobs/test_runner.py
  • scripts/smoke_openshell_isolation.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.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
**/*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/jobs/test_runner.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.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/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.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/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
🔇 Additional comments (9)
frontends/aiq_api/src/aiq_api/jobs/runner.py (1)

658-683: LGTM!

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

150-162: LGTM!

Also applies to: 194-209, 279-322

src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py (1)

36-46: LGTM!

Also applies to: 271-295, 316-334, 407-430, 432-501, 528-600

src/aiq_agent/agents/deep_researcher/sandbox/base.py (1)

102-103: LGTM!

Also applies to: 145-157, 210-216

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

26-27: LGTM!

Also applies to: 37-37, 82-83, 123-130, 400-438, 490-497, 500-529, 532-562, 565-620, 623-674, 814-841

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

20-21: LGTM!

Also applies to: 308-330, 332-334, 340-359, 361-380, 381-429

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

1911-1919: LGTM!

Also applies to: 1921-1930

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

292-336: LGTM!

scripts/smoke_openshell_isolation.py (1)

29-52: LGTM!

Also applies to: 55-72, 75-96, 98-142, 145-154, 156-188

@KyleZheng1284 KyleZheng1284 force-pushed the codex/aiq-openshell-isolation branch from e5b69ab to b05c93b Compare July 1, 2026 23:52

@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 (3)
frontends/aiq_api/src/aiq_api/jobs/runner.py (1)

649-650: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unguarded event_store.flush() in terminal finally block can mask the job result.

Every other cleanup call in this finally path (_teardown_sandbox) is deliberately exception-safe, per its own docstring: cleanup must never replace the job result. This new event_store.flush() call has no try/except — if _flush() raises, the exception propagates out of the finally block and overrides whatever result/exception the job actually produced. It's also invoked synchronously on the event loop rather than via asyncio.to_thread like the sandbox teardown right above it, so if _flush() does any blocking I/O it will stall the worker.

🛠️ Proposed fix: make flush best-effort and non-blocking
-        if event_store is not None and hasattr(event_store, "flush"):
-            event_store.flush()
+        if event_store is not None and hasattr(event_store, "flush"):
+            try:
+                await asyncio.to_thread(event_store.flush)
+            except Exception:  # noqa: BLE001 - flush must never replace the job result
+                logger.warning("Event store flush failed for job %s", job_id, exc_info=True)
🤖 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 `@frontends/aiq_api/src/aiq_api/jobs/runner.py` around lines 649 - 650, The new
event_store.flush() call in the runner’s terminal finally path should be made
best-effort like _teardown_sandbox so it cannot override the job outcome. Update
the cleanup in runner.py around the event_store handling to wrap flush in
exception-safe handling, and if the flush implementation may block, invoke it
off the event loop (consistent with the sandbox teardown pattern) instead of
calling it synchronously. Use the existing runner cleanup block and
event_store.flush as the locating symbols.
src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py (1)

403-405: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Respect delete_on_exit for job-owned sandboxes.

The per-job path always passes delete_on_exit=True, so a configured delete_on_exit=False is ignored. Keep the shared-attachment override at False, but use oscfg.delete_on_exit for owned sandboxes.

Proposed fix
             sandbox_kwargs.update(
                 spec=_build_sandbox_spec(policy=policy, image=oscfg.image, job_id=self.job_id),
-                delete_on_exit=True,
+                delete_on_exit=oscfg.delete_on_exit,
             )

As per path instructions, “Review agent changes for research workflow correctness, graph state transitions, prompt/tool contracts, HITL behavior, 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/providers/openshell.py` around
lines 403 - 405, The sandbox setup in openshell.py is hardcoding
delete_on_exit=True for the job-owned path, which overrides the configured
behavior. Update the sandbox_kwargs.update call in the owned-sandbox creation
flow to use oscfg.delete_on_exit, while keeping the shared-attachment override
path explicitly set to False. Make sure the change is applied in the same branch
that builds the sandbox spec via _build_sandbox_spec and assigns self.job_id.

Source: Path instructions

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

96-106: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Enforce the shared-sandbox debug opt-in in this config model.

Line 154 treats existing_sandbox_name/sandbox_name as a shared attachment, but the validator never rejects allow_shared_sandbox=False. Add an explicit check here so the NAT/runtime config cannot accept a non-isolated sandbox unless the debug opt-in is present.

Proposed fix
         if self.provider == "openshell":
             shared_name = self.existing_sandbox_name or self.sandbox_name
+            if shared_name and not self.allow_shared_sandbox:
+                raise ValueError("existing_sandbox_name/sandbox_name requires allow_shared_sandbox=true")
             if not shared_name and not self.attest:
                 raise ValueError("Per-job OpenShell creation requires attest=true")

As per path instructions, “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.”

Also applies to: 149-157

🤖 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/deepagents_runtime.py` around lines 96 -
106, The shared-sandbox debug opt-in is not enforced in this config model, so a
non-isolated attachment can be accepted even when allow_shared_sandbox is false.
Add an explicit validation check in the same config class that defines
existing_sandbox_name, sandbox_name, and allow_shared_sandbox to reject any use
of the shared sandbox fields unless the debug opt-in is enabled. Make sure the
validator covers both the primary and deprecated alias fields consistently.

Source: Path instructions

🤖 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 `@frontends/aiq_api/src/aiq_api/jobs/runner.py`:
- Around line 649-650: The new event_store.flush() call in the runner’s terminal
finally path should be made best-effort like _teardown_sandbox so it cannot
override the job outcome. Update the cleanup in runner.py around the event_store
handling to wrap flush in exception-safe handling, and if the flush
implementation may block, invoke it off the event loop (consistent with the
sandbox teardown pattern) instead of calling it synchronously. Use the existing
runner cleanup block and event_store.flush as the locating symbols.

In `@src/aiq_agent/agents/deep_researcher/deepagents_runtime.py`:
- Around line 96-106: The shared-sandbox debug opt-in is not enforced in this
config model, so a non-isolated attachment can be accepted even when
allow_shared_sandbox is false. Add an explicit validation check in the same
config class that defines existing_sandbox_name, sandbox_name, and
allow_shared_sandbox to reject any use of the shared sandbox fields unless the
debug opt-in is enabled. Make sure the validator covers both the primary and
deprecated alias fields consistently.

In `@src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py`:
- Around line 403-405: The sandbox setup in openshell.py is hardcoding
delete_on_exit=True for the job-owned path, which overrides the configured
behavior. Update the sandbox_kwargs.update call in the owned-sandbox creation
flow to use oscfg.delete_on_exit, while keeping the shared-attachment override
path explicitly set to False. Make sure the change is applied in the same branch
that builds the sandbox spec via _build_sandbox_spec and assigns self.job_id.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 1a4f355e-9777-4ffb-8f9e-c7419c6c4804

📥 Commits

Reviewing files that changed from the base of the PR and between e5b69ab and b05c93b.

📒 Files selected for processing (9)
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • scripts/smoke_openshell_isolation.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/jobs/test_runner.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.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:

  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/jobs/test_runner.py
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • scripts/smoke_openshell_isolation.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.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/jobs/test_runner.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
**

⚙️ CodeRabbit configuration file

**:

AI-Q Agent Guidance

Repository-global instructions for coding agents and for humans reviewing
agent-authored changes. These rules apply to every task in this repository.
Task-specific runbooks live in .agents/skills/ — load the
relevant skill before starting a workflow it covers.

Project overview

AI-Q is an NVIDIA AI Blueprint: an enterprise research agent built on the
NeMo Agent Toolkit (NAT). The deployed product is a research blueprint, not
a general skill runtime. New retrieval sources and tools are NAT functions;
agent behavior is driven by workflow YAML, Jinja2 prompts, and a data-source
registry — not by hard-coded logic.

Primary boundaries:

  • Backend Python package: src/aiq_agent/.
  • Data-source and tool packages: sources/ (each is its own package).
  • Frontends and tooling: frontends/ (web UI in frontends/ui/, eval harnesses
    in frontends/benchmarks/).
  • Configs, deployment, docs: configs/, deploy/, docs/.

Stay inside this repository. If your workspace also contains adjacent repos
(for example a sibling NeMo-Relay checkout), do not edit them as part of an AI-Q
change. Treat sources/* as independent packages: prefer the smallest change
scoped to the package you are touching.

Repository structure

Path Purpose
src/aiq_agent/ Backend agent, FastAPI extensions, auth, observability, knowledge
sources/ Data-source / tool packages (e.g. tavily_web_search, google_scholar_paper_search)
configs/ Workflow YAML configs (e.g. config_cli_default.yml)
frontends/ui/ Next.js / React / TypeScript / Tailwind / KUI web UI
frontends/benchmarks/ Eval harnesses: freshqa, deepsearch_qa, deepresearch_bench
deploy/ Docker Compose and Helm/Kubernetes assets; deploy/.env for secrets
docs/source/ ...

Files:

  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/jobs/test_runner.py
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • scripts/smoke_openshell_isolation.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.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
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/base.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.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/base.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
🔇 Additional comments (9)
frontends/aiq_api/src/aiq_api/jobs/runner.py (1)

658-683: LGTM! The finalize fast-path now logs a warning on a falsy cleanup_succeeded result, matching the previously requested fix and the TestTerminalTeardown test suite (test_runtime_finalizer_false_result_is_logged).

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

36-36: LGTM!

Also applies to: 92-95, 108-143, 160-162, 191-209, 279-322

src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py (1)

36-36: LGTM!

Also applies to: 46-46, 132-260, 272-273, 295-295, 316-316, 334-334, 390-392, 408-503, 530-602

src/aiq_agent/agents/deep_researcher/sandbox/base.py (1)

102-103: LGTM!

Also applies to: 145-157, 204-216

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

16-43: LGTM!

Also applies to: 71-161, 163-270, 271-705, 828-870

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

20-21: LGTM!

Also applies to: 37-37, 307-331, 332-335, 337-429

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

1911-1919: LGTM!

Also applies to: 1921-1931

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

292-337: LGTM!

scripts/smoke_openshell_isolation.py (1)

1-52: LGTM!

Also applies to: 55-73, 75-96, 98-142, 145-155, 156-184, 185-188, 192-197

@KyleZheng1284 KyleZheng1284 force-pushed the codex/aiq-openshell-isolation branch from b05c93b to 24264c4 Compare July 2, 2026 00:04

@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

Caution

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

⚠️ Outside diff range comments (3)
frontends/aiq_api/src/aiq_api/jobs/runner.py (1)

666-672: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Docstring doesn't mention the new finalize()-first path.

The docstring only describes the terminate()/close() routing, but the primary path (lines 675-682) now delegates to finalize() when present, falling back to terminate/close only for runtimes without it. Worth a one-line update for future maintainers.

📝 Suggested docstring update
     """Release sandbox resources on a terminal path (best-effort, never raises).
 
+    Prefers ``finalize(interrupted=...)`` when the runtime exposes it (logs a warning on a
+    falsy/failed result). Falls back to the legacy routing below for runtimes without a
+    ``finalize`` method:
     Interrupted jobs (cancel/timeout) call ``terminate()`` so a still-running ``execute`` is
     forcibly preempted; normal paths call ``close()`` gracefully. Both are idempotent. This runs
     off the event loop (``asyncio.to_thread``) so the SDK session close cannot block the worker.
     """
🤖 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 `@frontends/aiq_api/src/aiq_api/jobs/runner.py` around lines 666 - 672, Update
the _teardown_sandbox docstring in runner.py to mention the new finalize()-first
behavior before the existing terminate()/close() fallback description. Keep the
note brief but explicit that sandbox_runtime.finalize() is used when available,
with terminate() for interrupted jobs and close() for the remaining fallback
path. Use the _teardown_sandbox symbol so maintainers can quickly find the
routing logic.
src/aiq_agent/agents/deep_researcher/sandbox/base.py (1)

155-156: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Sanitize base lifecycle exception logging.

Both event emission and session cleanup are best-effort paths; avoid exc_info=True so secret-bearing callback/SDK details are not written to logs.

Suggested fix
-        except Exception:  # noqa: BLE001 - event persistence is non-critical
-            logger.warning("Sandbox event emission failed for %s", self.sandbox_name, exc_info=True)
+        except Exception as exc:  # noqa: BLE001 - event persistence is non-critical
+            logger.warning("Sandbox event emission failed for %s (%s)", self.sandbox_name, type(exc).__name__)
@@
-            except Exception:  # noqa: BLE001 - cleanup must never raise on the terminal path
+            except Exception as exc:  # noqa: BLE001 - cleanup must never raise on the terminal path
                 self._cleanup_failed = True
-                logger.warning("Sandbox %s cleanup failed", self.sandbox_name, exc_info=True)
+                logger.warning("Sandbox %s cleanup failed (%s)", self.sandbox_name, type(exc).__name__)

As per coding guidelines, “Never print or log secret values, including in tool output or error messages.”

Also applies to: 209-211

🤖 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/base.py` around lines 155 - 156,
The best-effort exception logging in the sandbox lifecycle currently includes
exc_info=True, which can leak secret-bearing callback or SDK details into logs.
Update the exception handlers in the base lifecycle methods (including the
sandbox event emission path and the related session cleanup path) to log only a
sanitized warning message without exception stack traces or raw exception
objects, keeping the existing logger.warning calls and using the same
identifiers like self.sandbox_name for context.

Source: Coding guidelines

src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py (1)

598-602: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Sanitize SDK cleanup exception logging.

The cleanup path should report failure without logging raw SDK exception text or traceback details.

Suggested fix
-            except Exception:  # noqa: BLE001 - cleanup must never raise on the terminal path
+            except Exception as exc:  # noqa: BLE001 - cleanup must never raise on the terminal path
                 self._cleanup_failed = True
-                logger.warning("OpenShell sandbox %s context cleanup failed", self.sandbox_name, exc_info=True)
+                logger.warning(
+                    "OpenShell sandbox %s context cleanup failed (%s)",
+                    self.sandbox_name,
+                    type(exc).__name__,
+                )

As per coding guidelines, “Never print or log secret values, including in tool output or error messages.”

🤖 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/providers/openshell.py` around
lines 598 - 602, The OpenShell cleanup path in the ctx.__exit__ exception
handler is logging raw SDK exception details via exc_info=True, which can expose
sensitive data. Update the cleanup logging in openshell.py to report only that
sandbox context cleanup failed, without including the exception text or
traceback, while still preserving the _cleanup_failed flag and the existing
warning in the cleanup flow.

Source: Coding guidelines

🤖 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/deepagents_runtime.py`:
- Around line 294-295: The cleanup warning in the terminal exception handler is
logging raw exception details via exc_info=True, which can leak sensitive
SDK/event-sink messages. Update the exception handling in the sandbox cleanup
path around the cleanup logic in deepagents_runtime.py (including the related
block near the other cleanup handler mentioned in the comment) to log only the
exception type or a sanitized summary, and remove full traceback/exception
payload logging from logger.warning while keeping the failure signal.
- Around line 154-156: The shared-sandbox alias handling in DeepAgentsRuntime is
too permissive because self.existing_sandbox_name or self.sandbox_name silently
prefers one value when both are set. Update the validation around shared_name so
DeepAgentsRuntime explicitly detects when existing_sandbox_name and deprecated
sandbox_name are both provided with different values and raises a ValueError
instead of choosing one. Keep the existing allow_shared_sandbox guard, and use
the existing_sandbox_name/sandbox_name checks in the same block to enforce a
single unambiguous sandbox name.

In `@src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py`:
- Around line 419-424: The attestation log in the OpenShell sandbox flow is
including an environment-specific gateway value that should not be emitted.
Update the logger.info call in the attestation path of the OpenShell provider to
remove oscfg.gateway from the message and its argument list, while keeping the
remaining identifiers like backend.id, sandbox_ref.name, policy_version, and
shared so the log stays useful without exposing deployment identifiers.

---

Outside diff comments:
In `@frontends/aiq_api/src/aiq_api/jobs/runner.py`:
- Around line 666-672: Update the _teardown_sandbox docstring in runner.py to
mention the new finalize()-first behavior before the existing
terminate()/close() fallback description. Keep the note brief but explicit that
sandbox_runtime.finalize() is used when available, with terminate() for
interrupted jobs and close() for the remaining fallback path. Use the
_teardown_sandbox symbol so maintainers can quickly find the routing logic.

In `@src/aiq_agent/agents/deep_researcher/sandbox/base.py`:
- Around line 155-156: The best-effort exception logging in the sandbox
lifecycle currently includes exc_info=True, which can leak secret-bearing
callback or SDK details into logs. Update the exception handlers in the base
lifecycle methods (including the sandbox event emission path and the related
session cleanup path) to log only a sanitized warning message without exception
stack traces or raw exception objects, keeping the existing logger.warning calls
and using the same identifiers like self.sandbox_name for context.

In `@src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py`:
- Around line 598-602: The OpenShell cleanup path in the ctx.__exit__ exception
handler is logging raw SDK exception details via exc_info=True, which can expose
sensitive data. Update the cleanup logging in openshell.py to report only that
sandbox context cleanup failed, without including the exception text or
traceback, while still preserving the _cleanup_failed flag and the existing
warning in the cleanup flow.
🪄 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: 870175a1-89e7-4cd7-b6c1-709d76de1100

📥 Commits

Reviewing files that changed from the base of the PR and between b05c93b and 24264c4.

📒 Files selected for processing (9)
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • scripts/smoke_openshell_isolation.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/jobs/test_runner.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.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:

  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • tests/aiq_agent/jobs/test_runner.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • scripts/smoke_openshell_isolation.py
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.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_deepagents_runtime.py
  • tests/aiq_agent/jobs/test_runner.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
**

⚙️ CodeRabbit configuration file

**:

AI-Q Agent Guidance

Repository-global instructions for coding agents and for humans reviewing
agent-authored changes. These rules apply to every task in this repository.
Task-specific runbooks live in .agents/skills/ — load the
relevant skill before starting a workflow it covers.

Project overview

AI-Q is an NVIDIA AI Blueprint: an enterprise research agent built on the
NeMo Agent Toolkit (NAT). The deployed product is a research blueprint, not
a general skill runtime. New retrieval sources and tools are NAT functions;
agent behavior is driven by workflow YAML, Jinja2 prompts, and a data-source
registry — not by hard-coded logic.

Primary boundaries:

  • Backend Python package: src/aiq_agent/.
  • Data-source and tool packages: sources/ (each is its own package).
  • Frontends and tooling: frontends/ (web UI in frontends/ui/, eval harnesses
    in frontends/benchmarks/).
  • Configs, deployment, docs: configs/, deploy/, docs/.

Stay inside this repository. If your workspace also contains adjacent repos
(for example a sibling NeMo-Relay checkout), do not edit them as part of an AI-Q
change. Treat sources/* as independent packages: prefer the smallest change
scoped to the package you are touching.

Repository structure

Path Purpose
src/aiq_agent/ Backend agent, FastAPI extensions, auth, observability, knowledge
sources/ Data-source / tool packages (e.g. tavily_web_search, google_scholar_paper_search)
configs/ Workflow YAML configs (e.g. config_cli_default.yml)
frontends/ui/ Next.js / React / TypeScript / Tailwind / KUI web UI
frontends/benchmarks/ Eval harnesses: freshqa, deepsearch_qa, deepresearch_bench
deploy/ Docker Compose and Helm/Kubernetes assets; deploy/.env for secrets
docs/source/ ...

Files:

  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • tests/aiq_agent/jobs/test_runner.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • scripts/smoke_openshell_isolation.py
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.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/base.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.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/base.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.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
🔇 Additional comments (10)
frontends/aiq_api/src/aiq_api/jobs/runner.py (2)

675-682: 🩺 Stability & Availability

Falsy finalize() return now logged — past review comment resolved.

This correctly closes the gap flagged previously: a non-raising finalize() failure is no longer silently dropped.


639-664: LGTM!

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

193-196: LGTM!

Also applies to: 210-211, 281-293, 297-303

src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py (1)

36-46: LGTM!

Also applies to: 271-295, 316-334, 390-418, 427-503, 530-597

src/aiq_agent/agents/deep_researcher/sandbox/base.py (1)

102-103: LGTM!

Also applies to: 145-154, 213-216

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

26-27: LGTM!

Also applies to: 37-37, 82-83, 123-130, 400-438, 458-498, 525-532, 535-709, 849-876

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

20-21: LGTM!

Also applies to: 336-343, 388-437

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

292-337: LGTM!

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

1911-1924: LGTM!

Also applies to: 1935-1945

scripts/smoke_openshell_isolation.py (1)

75-95: LGTM!

Also applies to: 166-171

Comment thread src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
Comment thread src/aiq_agent/agents/deep_researcher/deepagents_runtime.py Outdated
Comment thread src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py Outdated
@KyleZheng1284 KyleZheng1284 force-pushed the codex/aiq-openshell-isolation branch from 24264c4 to 8e2287b Compare July 2, 2026 00:20
@KyleZheng1284

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 2

Caution

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

⚠️ Outside diff range comments (2)
src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py (1)

597-601: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Sanitize OpenShell context cleanup failures.

exc_info=True can log raw SDK exception messages during terminal cleanup. Log only the exception type while preserving _cleanup_failed = True. As per coding guidelines, “Never print or log secret values, including in tool output or error messages.”

Suggested fix
-            except Exception:  # noqa: BLE001 - cleanup must never raise on the terminal path
+            except Exception as exc:  # noqa: BLE001 - cleanup must never raise on the terminal path
                 self._cleanup_failed = True
-                logger.warning("OpenShell sandbox %s context cleanup failed", self.sandbox_name, exc_info=True)
+                logger.warning(
+                    "OpenShell sandbox %s context cleanup failed (%s)",
+                    self.sandbox_name,
+                    type(exc).__name__,
+                )
🤖 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/providers/openshell.py` around
lines 597 - 601, The OpenShell cleanup warning in the context exit path is
leaking raw exception details via exc_info=True, which can expose sensitive SDK
messages. In the ctx.__exit__ exception handler inside the OpenShell sandbox
cleanup logic, keep setting _cleanup_failed = True but change the logger.warning
call to report only the exception type (using the caught exception object)
without full traceback or message details, and keep the context tied to the
existing sandbox_name identifier.

Source: Coding guidelines

src/aiq_agent/agents/deep_researcher/sandbox/base.py (1)

149-156: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not log raw event-sink or cleanup exceptions.

Both handlers use exc_info=True, which can emit secret-bearing exception messages from SDKs or persistence sinks. Keep these best-effort paths non-fatal but log only type(exc).__name__. As per coding guidelines, “Never print or log secret values, including in tool output or error messages.”

Suggested fix
-        except Exception:  # noqa: BLE001 - event persistence is non-critical
-            logger.warning("Sandbox event emission failed for %s", self.sandbox_name, exc_info=True)
+        except Exception as exc:  # noqa: BLE001 - event persistence is non-critical
+            logger.warning("Sandbox event emission failed for %s (%s)", self.sandbox_name, type(exc).__name__)
@@
-            except Exception:  # noqa: BLE001 - cleanup must never raise on the terminal path
+            except Exception as exc:  # noqa: BLE001 - cleanup must never raise on the terminal path
                 self._cleanup_failed = True
-                logger.warning("Sandbox %s cleanup failed", self.sandbox_name, exc_info=True)
+                logger.warning("Sandbox %s cleanup failed (%s)", self.sandbox_name, type(exc).__name__)

Also applies to: 204-211

🤖 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/base.py` around lines 149 - 156,
The best-effort error handlers in _emit_event and the cleanup path are logging
exception details with exc_info=True, which can leak secret-bearing messages
from SDKs or persistence sinks. Update these handlers to keep failures non-fatal
but log only the exception type name (for example via the caught Exception
object), and remove traceback/raw exception output from the logger.warning calls
while preserving sandbox_name context.

Source: Coding guidelines

🤖 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 `@frontends/aiq_api/src/aiq_api/jobs/runner.py`:
- Around line 680-681: The sandbox cleanup finalizer in `runner.py` still logs
full tracebacks via `logger.warning(..., exc_info=True)`, which can leak
secret-bearing details from unexpected SDK/event-sink exceptions. Update the
`finalize()` exception handler to log only the exception type/class name (using
the existing `job_id` context) and remove traceback emission so `Sandbox cleanup
failed for job %s` stays sanitized.

In `@tests/aiq_agent/jobs/test_runner.py`:
- Around line 1935-1945: The _teardown_sandbox path in aiq_api.jobs.runner still
logs finalize() exceptions with exc_info=True, which can leak raw exception
messages. Update the exception handling around runtime.finalize() to log only
the exception type or a sanitized summary, and add a regression test alongside
test_runtime_finalizer_false_result_is_logged that raises a RuntimeError with
sensitive text to confirm the warning does not expose the secret.

---

Outside diff comments:
In `@src/aiq_agent/agents/deep_researcher/sandbox/base.py`:
- Around line 149-156: The best-effort error handlers in _emit_event and the
cleanup path are logging exception details with exc_info=True, which can leak
secret-bearing messages from SDKs or persistence sinks. Update these handlers to
keep failures non-fatal but log only the exception type name (for example via
the caught Exception object), and remove traceback/raw exception output from the
logger.warning calls while preserving sandbox_name context.

In `@src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py`:
- Around line 597-601: The OpenShell cleanup warning in the context exit path is
leaking raw exception details via exc_info=True, which can expose sensitive SDK
messages. In the ctx.__exit__ exception handler inside the OpenShell sandbox
cleanup logic, keep setting _cleanup_failed = True but change the logger.warning
call to report only the exception type (using the caught exception object)
without full traceback or message details, and keep the context tied to the
existing sandbox_name identifier.
🪄 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: 30bb7492-02b8-46c4-87b0-b11bb1797bbf

📥 Commits

Reviewing files that changed from the base of the PR and between b05c93b and 8e2287b.

📒 Files selected for processing (9)
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • scripts/smoke_openshell_isolation.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/jobs/test_runner.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.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:

  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/jobs/test_runner.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • scripts/smoke_openshell_isolation.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.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/jobs/test_runner.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
**

⚙️ CodeRabbit configuration file

**:

AI-Q Agent Guidance

Repository-global instructions for coding agents and for humans reviewing
agent-authored changes. These rules apply to every task in this repository.
Task-specific runbooks live in .agents/skills/ — load the
relevant skill before starting a workflow it covers.

Project overview

AI-Q is an NVIDIA AI Blueprint: an enterprise research agent built on the
NeMo Agent Toolkit (NAT). The deployed product is a research blueprint, not
a general skill runtime. New retrieval sources and tools are NAT functions;
agent behavior is driven by workflow YAML, Jinja2 prompts, and a data-source
registry — not by hard-coded logic.

Primary boundaries:

  • Backend Python package: src/aiq_agent/.
  • Data-source and tool packages: sources/ (each is its own package).
  • Frontends and tooling: frontends/ (web UI in frontends/ui/, eval harnesses
    in frontends/benchmarks/).
  • Configs, deployment, docs: configs/, deploy/, docs/.

Stay inside this repository. If your workspace also contains adjacent repos
(for example a sibling NeMo-Relay checkout), do not edit them as part of an AI-Q
change. Treat sources/* as independent packages: prefer the smallest change
scoped to the package you are touching.

Repository structure

Path Purpose
src/aiq_agent/ Backend agent, FastAPI extensions, auth, observability, knowledge
sources/ Data-source / tool packages (e.g. tavily_web_search, google_scholar_paper_search)
configs/ Workflow YAML configs (e.g. config_cli_default.yml)
frontends/ui/ Next.js / React / TypeScript / Tailwind / KUI web UI
frontends/benchmarks/ Eval harnesses: freshqa, deepsearch_qa, deepresearch_bench
deploy/ Docker Compose and Helm/Kubernetes assets; deploy/.env for secrets
docs/source/ ...

Files:

  • tests/aiq_agent/agents/deep_researcher/test_agent.py
  • tests/aiq_agent/jobs/test_runner.py
  • src/aiq_agent/agents/deep_researcher/sandbox/base.py
  • frontends/aiq_api/src/aiq_api/jobs/runner.py
  • tests/aiq_agent/agents/deep_researcher/test_deepagents_runtime.py
  • scripts/smoke_openshell_isolation.py
  • tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_provider.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.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/base.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.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/base.py
  • src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
  • src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.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
🔇 Additional comments (9)
src/aiq_agent/agents/deep_researcher/deepagents_runtime.py (1)

154-162: LGTM!

Also applies to: 202-217, 287-309, 328-329

src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py (1)

36-46: LGTM!

Also applies to: 272-295, 316-334, 390-502, 529-596

src/aiq_agent/agents/deep_researcher/sandbox/base.py (1)

102-103: LGTM!

Also applies to: 213-216

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

641-663: LGTM!

Also applies to: 678-679

scripts/smoke_openshell_isolation.py (1)

75-95: LGTM!

Also applies to: 166-189

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

26-27: LGTM!

Also applies to: 37-37, 82-83, 123-130, 271-318, 411-449, 469-509, 536-720, 860-887

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

292-337: LGTM!

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

20-22: LGTM!

Also applies to: 337-352, 398-463

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

1911-1934: LGTM!

Comment thread frontends/aiq_api/src/aiq_api/jobs/runner.py Outdated
Comment thread tests/aiq_agent/jobs/test_runner.py
Comment thread scripts/smoke_openshell_isolation.py Outdated
Comment thread src/aiq_agent/agents/deep_researcher/deepagents_runtime.py
@AjayThorve

Copy link
Copy Markdown
Collaborator

/ok to test 9cec3b4

@AjayThorve AjayThorve left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I found five remaining blockers on the current head.

Comment thread src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py
Comment thread src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py Outdated
Comment thread src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py Outdated
Comment thread scripts/setup_openshell.sh Outdated
Comment thread src/aiq_agent/agents/deep_researcher/sandbox/providers/openshell.py Outdated

Copy link
Copy Markdown
Collaborator

Can we split durable OpenShell provisioning from gateway lifecycle before merging? setup_openshell.sh is documented as a run-once setup, but it currently installs dependencies, builds the policy/image, and also pkills and restarts a long-lived gateway. I’d keep setup idempotent and limited to install/configure/build, then add scripts/start_openshell_gateway.sh to start or reuse and register the gateway, with an actual create/delete probe as the readiness check. start_e2e.sh can optionally invoke that thin launcher; per-job sandbox creation should remain runtime-owned in Python. This gives the gateway one explicit lifecycle owner and prevents setup from reporting success for a service that cannot create the required sandbox.

@KyleZheng1284

Copy link
Copy Markdown
Contributor Author

Lifecycle follow-up: commit fc96a86 now makes setup_openshell.sh provisioning-only (pinned dependencies, policy generation/validation, image build). It cannot start/stop/kill/register/select a gateway or create a persistent debug sandbox, and legacy lifecycle flags fail with a migration message. Gateway ownership moved to start_openshell_gateway.sh, which accepts only an authenticated registered gateway or the official packaged Homebrew/systemd service, runs openshell status, and performs a mandatory disposable create/readiness/delete probe with trap cleanup. start_e2e.sh invokes it only with --start-openshell-gateway and never stops externally managed services. The stub lifecycle suite proves the auth rejection paths, mandatory probe, cleanup after probe failure, setup non-interference, and explicit E2E opt-in (6 passed in the post-merge run). The real Linux OpenShell 0.0.72 + Landlock hard_requirement probe is still pending on a suitable host; I am not substituting macOS best_effort output for that acceptance evidence.

@KyleZheng1284

Copy link
Copy Markdown
Contributor Author

/ok to test 0c25ad5

@KyleZheng1284

Copy link
Copy Markdown
Contributor Author

/ok to test 7baf293

@KyleZheng1284

Copy link
Copy Markdown
Contributor Author

/ok to test 170bdbf

@AjayThorve AjayThorve left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One test-ownership concern on the current head.

Comment thread scripts/smoke_openshell_isolation.py Outdated
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Live, deterministic proof of OpenShell per-job isolation and cleanup.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is a 300+ line live acceptance test: it imports internal provider helpers, creates real resources, asserts security and lifecycle behavior, and owns cleanup. Keeping that logic under scripts/ makes it look like an operator utility and leaves it outside pytest discovery, markers, and fixtures. Can we move the test implementation to tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_live.py, mark it integration, and gate it with AIQ_OPENSHELL_LIVE_TESTS=1, following the existing OpenSearch live-test pattern? If the one-command entry point is useful, keep a thin wrapper under scripts/, but the assertions and cleanup fixtures should be test-owned.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in dc9839c.

All live assertions and resource ownership now live in tests/aiq_agent/agents/deep_researcher/sandbox/test_openshell_live.py. The module is marked integration, is gated by AIQ_OPENSHELL_LIVE_TESTS=1, and does not import the optional OpenShell SDK or contact a gateway before the gate. Fixtures own configuration, provider creation, direct sandbox creation, immediate resource registration, reverse-order teardown, and gateway-verified deletion; teardown verification fails the test even if the test body also failed.

scripts/smoke_openshell_isolation.py is now only a subprocess.run() launcher that maps the compatibility flags to the documented environment variables, targets the single pytest module, and propagates pytest's exit code. tests/scripts/test_openshell_smoke_wrapper.py covers argument/environment mapping, best-effort opt-in, target selection, and exit-code propagation.

Local result without opt-in: 10 passed, 3 live tests collected/skipped, with no gateway access. The broader OpenShell regression set is 292 passed, 2 skipped. The required Linux/OpenShell 0.0.72/hard-Landlock live run is still pending, so I am intentionally leaving this thread open until that run and refreshed CI are green.

Copy link
Copy Markdown
Collaborator

Can we add a canonical OpenShell setup and deployment guide under docs/source—for example, docs/source/deployment/openshell.md—and have the existing script, sandbox, and architecture docs link to it? The current information is split across scripts/README.md, the sandbox implementation README, the architecture page, and config comments. The guide should provide one coherent operator contract: supported-platform matrix; macOS local-demo versus Linux production-acceptance flows; gateway installation and version compatibility; Docker/Podman and service ownership; Landlock policy/config pairing; provisioning versus startup responsibilities; deterministic live acceptance; and cleanup/troubleshooting. The architecture page can stay focused on invariants and scripts/README.md on command discovery. This feature introduces a real external runtime and security boundary, so it deserves a first-class user guide rather than requiring users to assemble the workflow from implementation notes.

@KyleZheng1284

Copy link
Copy Markdown
Contributor Author

Addressed in 450df92 with the canonical guide at docs/source/deployment/openshell.md.

The guide now owns the operator contract: security boundary; Linux/Docker, macOS demo, Podman evaluation, remote gateway, and Windows/WSL support matrix; pinned CLI/SDK/gateway/adapter compatibility; lifecycle/service ownership; exact Linux, macOS, and remote flows; policy/config and Landlock pairing; expected per-job behavior; pytest-owned acceptance; sanitized inspection; and safe cleanup/troubleshooting.

One-way references were added from docs/source/index.md, docs/source/deployment/index.md, docs/source/deployment/production.md, docs/source/resources/troubleshooting.md, docs/source/architecture/agents/sandbox.md, scripts/README.md, the sandbox implementation README, configs/config_openshell.yml, and setup/launcher help. Operational duplication was removed from the script and implementation READMEs.

The normal Sphinx build succeeds and the new guide/backlinks render without warnings. The requested warning-as-error build completes but remains nonzero on two pre-existing docs/source/integration/agent-skills.md links to .agents/skills/README; neither warning is in an OpenShell-touched source. I am leaving the review item open until refreshed CI and the required Linux/OpenShell 0.0.72/hard-Landlock live suite are green.

@KyleZheng1284 KyleZheng1284 added the BLOCKED Blocked on an external dependency or required acceptance criterion label Jul 7, 2026 — with ChatGPT Codex Connector

Copy link
Copy Markdown
Contributor Author

BLOCKED — upstream OpenShell policy state and SDK capabilities

Marking this PR blocked for the AI-Q 2.2 OpenShell integration.

The policy-status failure has now been reproduced without AI-Q by creating a sandbox directly through the OpenShell 0.0.77 CLI on macOS/Docker Desktop:

  • the sandbox reaches Ready;
  • policy get reports revision 2 as Effective;
  • policy list leaves revision 2 Pending while revision 1 is Superseded.

AI-Q therefore correctly times out instead of exposing the execution adapter without authoritative LOADED status. This is not explained by best_effort: that setting changes Landlock compatibility, not the policy-revision acknowledgement contract. The direct CLI reproduction also removes AI-Q's sandbox-creation path from the failure.

The upstream lifecycle/API work is tracked in NVIDIA/OpenShell#2159. That issue also tracks the Python SDK gap for request-level labels and selector-based listing; template/container labels alone do not make openshell sandbox list --selector aiq=deep-research work against gateway metadata.

Unblock criteria

  1. OpenShell reports an initially applied policy as LOADED (or FAILED) with an authoritative version, or documents another security-equivalent success contract.
  2. The public Python SDK supports request-level labels, selector filtering, and returned gateway metadata labels.
  3. The fixes are available in a tagged OpenShell release with compatible CLI, SDK, and gateway versions.
  4. AI-Q pins that release and passes the strict readiness probe.
  5. The pytest-owned live suite passes on macOS/Docker Desktop with explicit best_effort as demo validation and on Linux/Docker with hard_requirement as production acceptance.

Until those gates are met, this PR must not claim production acceptance or merge by weakening attestation, treating Pending as success, or pinning an unreleased fork. AI-Q-side fail-fast diagnostics, cleanup verification, tests, and documentation can continue while the upstream dependency is resolved.

Current scope note: the policy-state contradiction is confirmed on macOS with OpenShell 0.0.77; a minimal Linux reproduction is still required to determine whether the runtime manifestation is driver/platform-specific.

Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
The generated policy listed /app under filesystem_policy.read_only, but the
aiq-openshell-demo image has no /app directory. Under the default
landlock.compatibility: hard_requirement this fails closed at sandbox prepare
("Landlock path unavailable in hard_requirement mode: /app"), so the container
reaches Ready then immediately errors. Removing the path fixes prepare on all
hosts; production keeps hard_requirement, best_effort stays opt-in for local demos.

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>
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>
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>
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
@KyleZheng1284 KyleZheng1284 added AIQ2.2 and removed BLOCKED Blocked on an external dependency or required acceptance criterion labels Jul 10, 2026
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
@KyleZheng1284 KyleZheng1284 force-pushed the codex/aiq-openshell-isolation branch from 7b0ca2f to ddc1b6d Compare July 10, 2026 00:29
@KyleZheng1284

Copy link
Copy Markdown
Contributor Author

/ok to test ddc1b6d

@cdgamarose-nv

Copy link
Copy Markdown
Collaborator

If i run ./scripts/setup.sh followed by ./scripts/setup_openshell.sh, it will reinstall a bunch of packages with newer versions. Can we not just keep the versions in the two files in sync? And also not duplicate packages unless they're intended to be in different virtual environments?

Comment thread scripts/openshell/check_openshell_readiness.py
return getattr(message, field, None) is not None


def _deterministic_policy_hash(policy: Any) -> str:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is it safe that AI-Q is reinventing the recipe to compute the hash? It seems like there could be chances of it drifting from the openshell one, and keeping it up to date would be a lot of maintenance work

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the custom hash calculation and now validating that OpenShell’s effective and revision policies match the submitted policy and report the same non-empty hash.

Missing or mismatched hashes fail closed; if we need local hash verification later we can look into using an official OpenShell SDK helper

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

Copy link
Copy Markdown
Contributor Author

If i run ./scripts/setup.sh followed by ./scripts/setup_openshell.sh, it will reinstall a bunch of packages with newer versions. Can we not just keep the versions in the two files in sync? And also not duplicate packages unless they're intended to be in different virtual environments?

there are some deps mismatches since the published langchain-openshell pkg 0.1.0 still declares deepagents < 0.6 while AIQ is currently 0.6.8. I removed the forced reinstall and now restore AI-Q’s locked dependencies after installing OpenShell; we can switch to a single uv sync --extra openshell once the adapter supports DeepAgents 0.6.x.

@KyleZheng1284

Copy link
Copy Markdown
Contributor Author

/ok to test d74c86c

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