Skip to content

Add Bounded Research Specialists - #107

Merged
PSchmiedmayer merged 7 commits into
mainfrom
feature/specialized-research-agents
Aug 3, 2026
Merged

Add Bounded Research Specialists#107
PSchmiedmayer merged 7 commits into
mainfrom
feature/specialized-research-agents

Conversation

@PSchmiedmayer

@PSchmiedmayer PSchmiedmayer commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

♻️ Current Situation & Problem

Closes #99.

Heartwood had one tool-free planner but no maintained research-specialist catalog or shared discovery surface. Tool-enabled child work must remain disabled until OpenHands provides restart-safe child approval and cancellation.

⚙️ Release Notes

  • Add bounded planning, data-quality, cohort, statistical, and reproducibility specialists through OpenHands' native Task workflow.
  • Expose one gateway-owned catalog in the terminal, browser, and notebook bridge, with persisted lifecycle and minimized audit evidence.
  • Keep the analysis implementation role visible but unavailable, and prepare the repository for 0.3.0-beta.1.

📚 Documentation

  • Add the research-specialist workflow and architecture boundary.
  • Refresh the README, platform references, and browser screenshots for the prerelease.

✅ Testing

  • 1,577 Python tests with 92.14% coverage and 97.07% patch coverage locally.
  • 122 browser unit tests, five Playwright flows, and strict type, lint, format, license, duplication, and contract checks.
  • Network-disabled OpenHands delegation, Jupyter proxy, gateway, documentation publication, and committed native-archive smoke tests.

Code of Conduct & Contributing Guidelines

By creating and submitting this pull request, you agree to follow our Code of Conduct and Contributing Guidelines:

Copilot AI review requested due to automatic review settings August 3, 2026 15:49
@github-project-automation github-project-automation Bot moved this to Backlog in Heartwood Aug 3, 2026

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PSchmiedmayer, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6c71aa06-a814-4f61-816d-fb29cbf4ddab

📥 Commits

Reviewing files that changed from the base of the PR and between 0d30785 and e3c28bd.

⛔ Files ignored due to path filters (1)
  • packages/webui/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • packages/webui/package.json
📝 Walkthrough

Walkthrough

This release adds a validated catalog of bounded research specialists. The gateway exposes specialist settings through REST, CLI, terminal, notebook, and Web UI interfaces. OpenHands delegation supports sequential execution, cancellation, resumption, failure projection, persistence, and replay. Tool-enabled analysis implementation remains unavailable. Documentation, smoke tests, screenshots, and package versions target 0.3.0-beta.1.

Sequence Diagram(s)

sequenceDiagram
  participant ParentAgent
  participant Gateway
  participant Specialist
  participant Client
  ParentAgent->>Gateway: submit bounded specialist task
  Gateway->>Specialist: execute catalog-approved role
  Specialist-->>Gateway: return review result
  Gateway-->>ParentAgent: project lifecycle and result
  Gateway-->>Client: expose settings and status
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding bounded research specialists.
Description check ✅ Passed The description directly covers the specialist catalog, interfaces, safeguards, documentation, release updates, and validation.
Linked Issues check ✅ Passed The changes address the linked issue through bounded roles, gateway-owned discovery, sequential delegation, safety controls, lifecycle projection, client parity, and tests.
Out of Scope Changes check ✅ Passed The documentation, release updates, screenshots, and tests support the linked specialist objectives and are not unrelated changes.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 8

🧹 Nitpick comments (8)
packages/gateway/src/heartwood/gateway/_specialists.py (1)

253-266: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Confirm the intended dead combination for project-actions roles.

Lines 255 and 259 combine into an unreachable state: a project-actions specialist that is available must declare tools (line 255) and must not declare tools (line 259). Every tool-using role therefore has to stay unavailable. That matches the PR objective, so no change is required now. Add a short comment that records this constraint, so a later reader does not read line 255 as a supported path.

🤖 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 `@packages/gateway/src/heartwood/gateway/_specialists.py` around lines 253 -
266, Add a brief comment near the capability and availability validation in the
specialist definition flow, anchored around SpecialistCapability.PROJECT_ACTIONS
and SpecialistAvailability.AVAILABLE, documenting that project-actions
specialists must remain unavailable because available specialists cannot declare
tools. Do not alter the existing validation behavior.
packages/gateway/src/heartwood/gateway/_openhands_sdk.py (2)

1556-1562: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider preserving the failure class for specialist failures.

Every failed TaskObservation now projects the same sentence. A researcher cannot tell a budget exhaustion from an iteration-limit stop or a model error. The child text stays out of the projection, which is the intended boundary, but a bounded, enumerated reason keeps the parent view actionable.

Consider mapping the observation to a small fixed set of reasons, for example "budget exhausted", "iteration limit reached", or "specialist error", and appending that reason to the message. Keep the free-form child text excluded.

🤖 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 `@packages/gateway/src/heartwood/gateway/_openhands_sdk.py` around lines 1556 -
1562, Update the failed TaskObservation branch in the observation result
projection to classify failures using a small fixed set of bounded reasons, such
as budget exhaustion, iteration-limit reached, or specialist error, and append
the selected reason to the parent-facing message. Preserve the existing
exclusion of free-form child text and keep result_truncated false for these
classified failures.

1453-1459: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the description check.

The outer truthiness test on action.description is redundant. The inner check already rejects empty and whitespace-only values.

♻️ Proposed simplification
 def _tool_summary(event: ActionEvent, *, tool_name: str) -> str:
     action = event.action
-    if isinstance(action, TaskAction) and action.description:
-        description = action.description.strip()
-        if description:
-            return description
+    if isinstance(action, TaskAction):
+        description = action.description.strip()
+        if description:
+            return description
     return event.summary or f"run {tool_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 `@packages/gateway/src/heartwood/gateway/_openhands_sdk.py` around lines 1453 -
1459, In _tool_summary, remove the redundant outer truthiness check around
action.description and let the existing strip-and-nonempty check handle empty or
whitespace-only descriptions while preserving the fallback to event.summary or
“run {tool_name}”.
packages/gateway/tests/test_specialists.py (1)

82-106: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use one substitution idiom for the boundary-widening cases.

Lines 97-102 assign source twice. The first replace looks for permission_mode: inherit in the permission_mode case, which never matches, and the branch then discards the result. The same file already uses a clearer (old, new, message) parametrization at lines 179-192. Reuse it here.

♻️ Proposed refactor
 `@pytest.mark.parametrize`(
-    ("field", "value", "message"),
+    ("old", "new", "message"),
     [
-        ("model", "openai/unreviewed", "inherit the parent model route"),
-        ("permission_mode", "never_confirm", "must use always_confirm"),
+        ("model: inherit", "model: openai/unreviewed", "inherit the parent model route"),
+        (
+            "permission_mode: always_confirm",
+            "permission_mode: never_confirm",
+            "must use always_confirm",
+        ),
     ],
 )
 def test_catalog_rejects_role_boundary_widening(
-    field: str,
-    value: str,
+    old: str,
+    new: str,
     message: str,
     tmp_path: Path,
 ) -> None:
     agents_dir = tmp_path / "agents"
     agents_dir.mkdir()
-    source = _valid_definition().replace(f"{field}: inherit", f"{field}: {value}")
-    if field == "permission_mode":
-        source = _valid_definition().replace(
-            "permission_mode: always_confirm",
-            f"permission_mode: {value}",
-        )
+    source = _valid_definition().replace(old, new)
     (agents_dir / "bounded-reviewer.md").write_text(source, encoding="utf-8")
🤖 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 `@packages/gateway/tests/test_specialists.py` around lines 82 - 106, Refactor
test_catalog_rejects_role_boundary_widening to parameterize each case with the
original text, replacement text, and expected message, matching the existing
pattern used later in the file. Remove the field-based conditional and perform
one source.replace operation per case so both model and permission_mode
substitutions target the correct baseline values.
packages/gateway/tests/test_openhands_sdk.py (2)

3519-3523: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

_specialist_catalog() reloads the catalog from disk on every call. The helper reads and parses every file under agents/verified and skills/verified each time. Several tests call it repeatedly, and line 2560 calls it inside a generator expression, so it runs once per evaluated event.

  • packages/gateway/tests/test_openhands_sdk.py#L3519-L3523: cache the loaded catalog, for example with functools.lru_cache on the helper or a module-level session fixture.
  • packages/gateway/tests/test_openhands_sdk.py#L2560-L2560: hoist the expected label into a local variable before the assert any(...) so the catalog is not reloaded per event.
🤖 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 `@packages/gateway/tests/test_openhands_sdk.py` around lines 3519 - 3523, Cache
the catalog returned by _specialist_catalog() so its disk loading and parsing
occur only once per test process or session; use functools.lru_cache or an
equivalent session-scoped fixture. In
packages/gateway/tests/test_openhands_sdk.py lines 3519-3523, apply the cache to
_specialist_catalog. In packages/gateway/tests/test_openhands_sdk.py line 2560,
hoist the expected label into a local variable before the any(...) assertion so
the catalog lookup is not repeated for each event.

2828-2834: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid hardcoding the catalog label in the replay assertion.

Line 2831 hardcodes "Cohort and Feature Reviewer". The other specialist test reads the label from the catalog. If the label in agents/verified/cohort-feature-reviewer.md changes, this test fails for a reason unrelated to replay. Read the label from the catalog, as line 2560 does.

♻️ Proposed change
+    expected_label = _specialist_catalog().role("cohort-feature-reviewer").label
     assert any(
         isinstance(event, BackendSubagentEvent)
         and event.subagent.agent_name == "cohort-feature-reviewer"
-        and event.subagent.role_label == "Cohort and Feature Reviewer"
+        and event.subagent.role_label == expected_label
         and event.subagent.status == BackendSubagentStatus.COMPLETED
         for event in replayed
     )
🤖 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 `@packages/gateway/tests/test_openhands_sdk.py` around lines 2828 - 2834,
Update the replay assertion for cohort-feature-reviewer to obtain the expected
role label from the catalog, matching the existing pattern used by the
specialist test near line 2560, instead of hardcoding "Cohort and Feature
Reviewer"; keep the remaining BackendSubagentEvent and completion checks
unchanged.
packages/webui/src/components/UtilitySheet.tsx (1)

344-347: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Render the specialist summary from shared role metadata.

SpecialistSettings already carries capability and model_route, but this component hard-codes Advisory and Uses the active model. The browser will display incorrect metadata if the gateway enables a role with different values, and it can diverge from the CLI and notebook projections. Use a gateway-provided summary or a shared formatter.

Suggested direction
-                  Advisory · Uses the active model · Up to {role.max_iterations}{" "}
-                  steps
+                  {specialistPresentationSummary(role)}

Based on learnings, keep business rules and labels in the gateway or owning typed package; browser code should only adapt shared projections.

🤖 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 `@packages/webui/src/components/UtilitySheet.tsx` around lines 344 - 347,
Update the specialist summary rendering in UtilitySheet to consume the shared
gateway role metadata or formatter, using SpecialistSettings.capability and
model_route instead of hard-coded “Advisory” and “Uses the active model” labels.
Keep the browser component limited to adapting the shared projection while
preserving the role.max_iterations step count.

Source: Learnings

packages/webui/src/e2e/app.spec.ts (1)

93-99: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Cover the visible-but-unavailable role in the browser contract test.

Lines 93-99 verify only the available Research Planner.
The mocked response in Lines 1029-1048 omits Analysis Implementer, although this release must keep that role visible with availability: "unavailable".
Add the unavailable role to the mock and assert its label and unavailable reason in the panel.
Otherwise, a regression that removes the unavailable entry or exposes it as available can pass this test.

As per coding guidelines, changed web-UI behavior requires tests; the PR objective requires Analysis Implementer to remain visible but unavailable until restart-safe child approval and cancellation are supported.

Also applies to: 1029-1048

🤖 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 `@packages/webui/src/e2e/app.spec.ts` around lines 93 - 99, Update the browser
contract test around the Specialists panel and its mocked response to include an
Analysis Implementer entry with availability set to unavailable. Assert that its
label remains visible and that the panel displays the expected unavailable
reason, while preserving the existing Research Planner assertion.

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 `@documentation/assets/screenshots/browser-specialists.png.license`:
- Around line 1-3: Update the SPDX attribution in the browser-specialists
license sidecar to include the required Schmiedmayer Lab at Stanford University
attribution while preserving the canonical “2026 Stanford University and the
project authors (see CONTRIBUTORS.md)” copyright text; do not duplicate or alter
that SPDX copyright line.

In `@documentation/reference/glossary.md`:
- Around line 111-113: Update the Research specialist glossary definition to
state that its inherited project, credential, model, network, policy, and
confirmation boundaries may only be inherited or narrowed, never broadened
beyond the parent agent’s boundaries.

In `@documentation/use/browser.md`:
- Line 60: Update the navigation instruction in browser.md to use “research
specialists” consistently instead of “bounded reviewers,” reflecting that the
catalog includes planning and review roles and matching the existing navigation
and glossary terminology.

In `@documentation/use/specialists.md`:
- Around line 62-64: Update the Analysis Implementer documentation to
distinguish catalog visibility from execution: state that the role remains
visible in the gateway-owned catalog but is unavailable, while Heartwood does
not register it as an executable OpenHands specialist or grant it project tools.
Preserve the conservative condition that it remains unavailable until
restart-safe child approval and cancellation are supported.
- Line 3: Update the SPDX copyright header in specialists.md to use the
repository’s canonical attribution naming only the Schmiedmayer Lab at Stanford
University, without adding legacy organizational attributions.

In `@images/generic/scripts/local_model_stub.py`:
- Around line 238-242: Update specialist completion detection in the current
task flow around has_specialist_result and has_execution_result to inspect only
tool_results produced after task_index, rather than serialized_messages
containing the full conversation history. Ensure each cohort request
independently invokes research-planner when needed and retains the existing
terminal-action behavior after current-task completion; add a regression case
covering two cohort requests in one session.

In `@packages/schemas/tests/test_api_contracts.py`:
- Around line 89-111: Refactor
test_specialist_response_rejects_unsafe_contract_drift to build a valid
SpecialistSettingsResponse payload, then add parametrized cases that mutate
exactly one policy boundary at a time, covering route, tool, permission,
max_iterations, and max_budget_usd constraints. Keep each case’s expected
ValidationError assertion independent so a regression in any single boundary is
detected.

In `@packages/webui/src/styles.css`:
- Around line 1720-1722: Update the .specialist-row.unavailable style to remove
the opacity declaration so inherited text remains fully opaque, and distinguish
unavailable rows using a border or background treatment instead.

---

Nitpick comments:
In `@packages/gateway/src/heartwood/gateway/_openhands_sdk.py`:
- Around line 1556-1562: Update the failed TaskObservation branch in the
observation result projection to classify failures using a small fixed set of
bounded reasons, such as budget exhaustion, iteration-limit reached, or
specialist error, and append the selected reason to the parent-facing message.
Preserve the existing exclusion of free-form child text and keep
result_truncated false for these classified failures.
- Around line 1453-1459: In _tool_summary, remove the redundant outer truthiness
check around action.description and let the existing strip-and-nonempty check
handle empty or whitespace-only descriptions while preserving the fallback to
event.summary or “run {tool_name}”.

In `@packages/gateway/src/heartwood/gateway/_specialists.py`:
- Around line 253-266: Add a brief comment near the capability and availability
validation in the specialist definition flow, anchored around
SpecialistCapability.PROJECT_ACTIONS and SpecialistAvailability.AVAILABLE,
documenting that project-actions specialists must remain unavailable because
available specialists cannot declare tools. Do not alter the existing validation
behavior.

In `@packages/gateway/tests/test_openhands_sdk.py`:
- Around line 3519-3523: Cache the catalog returned by _specialist_catalog() so
its disk loading and parsing occur only once per test process or session; use
functools.lru_cache or an equivalent session-scoped fixture. In
packages/gateway/tests/test_openhands_sdk.py lines 3519-3523, apply the cache to
_specialist_catalog. In packages/gateway/tests/test_openhands_sdk.py line 2560,
hoist the expected label into a local variable before the any(...) assertion so
the catalog lookup is not repeated for each event.
- Around line 2828-2834: Update the replay assertion for cohort-feature-reviewer
to obtain the expected role label from the catalog, matching the existing
pattern used by the specialist test near line 2560, instead of hardcoding
"Cohort and Feature Reviewer"; keep the remaining BackendSubagentEvent and
completion checks unchanged.

In `@packages/gateway/tests/test_specialists.py`:
- Around line 82-106: Refactor test_catalog_rejects_role_boundary_widening to
parameterize each case with the original text, replacement text, and expected
message, matching the existing pattern used later in the file. Remove the
field-based conditional and perform one source.replace operation per case so
both model and permission_mode substitutions target the correct baseline values.

In `@packages/webui/src/components/UtilitySheet.tsx`:
- Around line 344-347: Update the specialist summary rendering in UtilitySheet
to consume the shared gateway role metadata or formatter, using
SpecialistSettings.capability and model_route instead of hard-coded “Advisory”
and “Uses the active model” labels. Keep the browser component limited to
adapting the shared projection while preserving the role.max_iterations step
count.

In `@packages/webui/src/e2e/app.spec.ts`:
- Around line 93-99: Update the browser contract test around the Specialists
panel and its mocked response to include an Analysis Implementer entry with
availability set to unavailable. Assert that its label remains visible and that
the panel displays the expected unavailable reason, while preserving the
existing Research Planner assertion.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ca556431-8dde-4bc5-a4a2-2f6926929954

📥 Commits

Reviewing files that changed from the base of the PR and between c1ae61f and 12ee178.

⛔ Files ignored due to path filters (9)
  • documentation/assets/screenshots/browser-action-review.png is excluded by !**/*.png
  • documentation/assets/screenshots/browser-action-settings.png is excluded by !**/*.png
  • documentation/assets/screenshots/browser-changes.png is excluded by !**/*.png
  • documentation/assets/screenshots/browser-conversation.png is excluded by !**/*.png
  • documentation/assets/screenshots/browser-files.png is excluded by !**/*.png
  • documentation/assets/screenshots/browser-specialists.png is excluded by !**/*.png
  • packages/webui/package-lock.json is excluded by !**/package-lock.json
  • packages/webui/src/apiTypes.generated.ts is excluded by !**/*.generated.*
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (92)
  • AGENTS.md
  • README.md
  • VERSION.toml
  • agents/verified/analysis-implementer.md
  • agents/verified/cohort-feature-reviewer.md
  • agents/verified/data-quality-reviewer.md
  • agents/verified/reproducibility-reviewer.md
  • agents/verified/research-planner.md
  • agents/verified/statistical-reviewer.md
  • deploy/tests/native_installer_real_smoke.sh
  • docker-bake.hcl
  • documentation/architecture/system.md
  • documentation/assets/screenshots/browser-specialists.png.license
  • documentation/index.md
  • documentation/models/offline.md
  • documentation/platforms/carina.md
  • documentation/platforms/containers.md
  • documentation/platforms/native-linux.md
  • documentation/platforms/terra.md
  • documentation/reference/cli.md
  • documentation/reference/glossary.md
  • documentation/use/actions-audit.md
  • documentation/use/browser.md
  • documentation/use/index.md
  • documentation/use/notebooks.md
  • documentation/use/specialists.md
  • documentation/use/terminal.md
  • fixtures/synthetic/skills/omop-cohort-summary/SKILL.md
  • fixtures/synthetic/skills/omop-cohort-summary/metadata.json
  • images/generic/scripts/local_model_stub.py
  • packages/adapters/pyproject.toml
  • packages/adapters/src/heartwood/adapters/__init__.py
  • packages/audit/pyproject.toml
  • packages/audit/src/heartwood/audit/__init__.py
  • packages/cli/pyproject.toml
  • packages/cli/src/heartwood/cli/__init__.py
  • packages/cli/src/heartwood/cli/_interactive.py
  • packages/cli/tests/test_cli.py
  • packages/cli/tests/test_interactive.py
  • packages/compliance/pyproject.toml
  • packages/compliance/src/heartwood/compliance/__init__.py
  • packages/compliance/tests/test_container_assets.py
  • packages/core-adapter/pyproject.toml
  • packages/core-adapter/src/heartwood/core_adapter/__init__.py
  • packages/core-adapter/src/heartwood/core_adapter/_facade.py
  • packages/core-adapter/src/heartwood/core_adapter/_service.py
  • packages/detector/pyproject.toml
  • packages/detector/src/heartwood/detector/__init__.py
  • packages/fixtures/pyproject.toml
  • packages/fixtures/src/heartwood/fixtures/__init__.py
  • packages/gateway/pyproject.toml
  • packages/gateway/src/heartwood/gateway/_gateway.py
  • packages/gateway/src/heartwood/gateway/_openhands_sdk.py
  • packages/gateway/src/heartwood/gateway/_rest.py
  • packages/gateway/src/heartwood/gateway/_session_projection.py
  • packages/gateway/src/heartwood/gateway/_specialists.py
  • packages/gateway/tests/test_gateway_contract.py
  • packages/gateway/tests/test_openhands_sdk.py
  • packages/gateway/tests/test_specialists.py
  • packages/model-policy/pyproject.toml
  • packages/model-policy/src/heartwood/model_policy/__init__.py
  • packages/notebook/pyproject.toml
  • packages/notebook/src/heartwood/notebook/__init__.py
  • packages/notebook/src/heartwood/notebook/_view_model.py
  • packages/notebook/tests/test_notebook.py
  • packages/persistence/pyproject.toml
  • packages/persistence/src/heartwood/persistence/__init__.py
  • packages/schemas/pyproject.toml
  • packages/schemas/src/heartwood/schemas/__init__.py
  • packages/schemas/src/heartwood/schemas/_api.py
  • packages/schemas/tests/test_api_contracts.py
  • packages/session/pyproject.toml
  • packages/session/src/heartwood/session/__init__.py
  • packages/skills/pyproject.toml
  • packages/webui/package.json
  • packages/webui/scripts/smoke-reference-analysis.cjs
  • packages/webui/src/App.test.tsx
  • packages/webui/src/App.tsx
  • packages/webui/src/client.test.ts
  • packages/webui/src/client.ts
  • packages/webui/src/components/SessionRail.tsx
  • packages/webui/src/components/UtilitySheet.tsx
  • packages/webui/src/e2e/app.spec.ts
  • packages/webui/src/styles.css
  • packages/webui/src/types.ts
  • skills/verified/aggregate-export/SKILL.md
  • skills/verified/aggregate-export/metadata.json
  • skills/verified/baseline-model/SKILL.md
  • skills/verified/baseline-model/metadata.json
  • skills/verified/omop-cohort-summary/SKILL.md
  • skills/verified/omop-cohort-summary/metadata.json
  • zensical.toml

Comment thread documentation/assets/screenshots/browser-specialists.png.license
Comment thread documentation/reference/glossary.md
Comment thread documentation/use/browser.md Outdated
Comment thread documentation/use/specialists.md
Comment thread documentation/use/specialists.md Outdated
Comment thread images/generic/scripts/local_model_stub.py Outdated
Comment thread packages/schemas/tests/test_api_contracts.py Outdated
Comment thread packages/webui/src/styles.css
Copilot AI review requested due to automatic review settings August 3, 2026 16:16

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 3, 2026 16:30

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 3, 2026 16:38

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 3, 2026 16:45

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 3, 2026 17:57

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@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.

🧹 Nitpick comments (8)
packages/gateway/tests/test_specialist_task.py (1)

24-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for _validated_roles.

_validated_roles is the input boundary that builds the allowlist. Its three rejection paths are untested: an empty specialist list, incomplete metadata, and duplicate specialist_id values. Add cases so a regression in the allowlist construction fails a test.

♻️ Proposed additional tests
def test_validated_roles_rejects_empty_incomplete_and_duplicate_metadata() -> None:
    with pytest.raises(ValueError, match="At least one available specialist"):
        _validated_roles([])
    with pytest.raises(ValueError, match="metadata must be complete"):
        _validated_roles(
            [{"specialist_id": "research-planner", "label": " ", "description": "Plan."}]
        )
    role: SpecialistToolRole = {
        "specialist_id": "research-planner",
        "label": "Research Planner",
        "description": "Plan the analysis.",
    }
    with pytest.raises(ValueError, match="identifiers must be unique"):
        _validated_roles([role, dict(role)])  # type: ignore[list-item]

As per coding guidelines: "Add or update tests when changing detector logic, policy decisions, adapter behavior, skill validation, audit records, attestation export, CLI output, notebook view models, or web-UI view models."

🤖 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 `@packages/gateway/tests/test_specialist_task.py` around lines 24 - 45, Add a
test covering all three rejection paths in _validated_roles: empty specialist
input, incomplete metadata, and duplicate specialist_id values. Assert each
raises ValueError with the expected message, using a valid SpecialistToolRole
duplicated for the uniqueness case.

Source: Coding guidelines

packages/gateway/tests/test_openhands_persistence.py (1)

143-151: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Also assert the lifecycle fields survive minimization.

The projection needs task_id and subagent from the errored observation to render the specialist as stopped with an error. Add assertions so a future change to _minimize_event cannot drop them silently.

♻️ Proposed additional assertions
     persisted = (root / "events" / "event-00000-12345678.json").read_text(encoding="utf-8")
     assert "participant-secret" not in persisted
     assert "/private/project" not in persisted
     assert "The agent could not complete the requested action" in persisted
+    assert "task_00000001" in persisted
+    assert "data-quality-reviewer" in persisted
🤖 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 `@packages/gateway/tests/test_openhands_persistence.py` around lines 143 - 151,
Extend the persistence assertions for the minimized errored observation written
by store.write to verify that its task_id and subagent lifecycle fields remain
present in persisted. Keep the existing secret, path, and error-message
assertions unchanged, and assert the expected field values from the event
fixture.
packages/notebook/src/heartwood/notebook/_widgets.py (2)

99-114: 📐 Maintainability & Code Quality | 🔵 Trivial

Capability wording differs from WebUI (see consolidated comment).

This is addressed in the consolidated comments section together with the equivalent CLI and WebUI locations.

🤖 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 `@packages/notebook/src/heartwood/notebook/_widgets.py` around lines 99 - 114,
Update the capability label formatting in the action display loop around
action.details.capability to match the wording and formatting used by the WebUI
and CLI implementations; keep the existing task-only conditional and capability
presence checks unchanged.

127-129: 🎯 Functional Correctness | 🔵 Trivial

Task-heading fallback order differs from CLI (see consolidated comment).

This is addressed in the consolidated comments section together with the equivalent CLI and WebUI locations.

🤖 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 `@packages/notebook/src/heartwood/notebook/_widgets.py` around lines 127 - 129,
Align the task-heading fallback expression assigned to label with the canonical
fallback order used by the CLI and WebUI. Update the ordering of
details.role_label, details.description, details.subagent_type, and
action.summary consistently, preserving the existing values and fallback
behavior.
packages/webui/src/components/ConversationWorkspace.tsx (2)

767-798: 📐 Maintainability & Code Quality | 🔵 Trivial

Capability wording is hardcoded and diverges from CLI/Notebook (see consolidated comment).

This is addressed in the consolidated comments section together with the equivalent CLI and Notebook locations.

🤖 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 `@packages/webui/src/components/ConversationWorkspace.tsx` around lines 767 -
798, Update the capability label rendering in ConversationWorkspace around
specialist.capability to reuse the shared capability-to-display-label mapping
used by the CLI and Notebook, instead of hardcoding “Advisory review” and
“Project actions.” Preserve the existing conditional rendering and risk badge
behavior.

Source: Coding guidelines


566-572: 🎯 Functional Correctness | 🔵 Trivial

Task-heading fallback order differs from CLI (see consolidated comment).

This is addressed in the consolidated comments section together with the equivalent CLI and Notebook locations.

🤖 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 `@packages/webui/src/components/ConversationWorkspace.tsx` around lines 566 -
572, Align the task-heading fallback chain in ConversationWorkspace with the
canonical ordering used by the equivalent CLI and Notebook implementations.
Update the action.details.kind === "task" branch to use the same field
precedence, preserving the final action.summary fallback.
images/generic/scripts/local_model_stub.py (1)

252-256: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate specialist-result check.

has_execution_result re-implements the "specialist review complete:" substring check already encapsulated in _has_specialist_result. Derive it from that helper instead of duplicating the literal string check.

♻️ Proposed refactor
-        has_execution_result = any(
-            "specialist review complete:" not in json.dumps(result).lower()
-            for result in tool_results
-        )
+        has_execution_result = bool(tool_results) and not all(
+            "specialist review complete:" in json.dumps(result).lower()
+            for result in tool_results
+        )
🤖 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 `@images/generic/scripts/local_model_stub.py` around lines 252 - 256, Update
the result-status logic near _has_specialist_result so has_execution_result
derives from the existing has_specialist_result value instead of repeating the
"specialist review complete:" check over tool_results. Remove the duplicated
json.dumps-based condition while preserving the intended execution-result
behavior.
packages/cli/src/heartwood/cli/_interactive.py (1)

532-534: 🎯 Functional Correctness | 🔵 Trivial

Task-heading fallback order differs from Notebook/WebUI (see consolidated comment).

This is addressed in the consolidated comments section together with the equivalent Notebook and WebUI locations.

🤖 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 `@packages/cli/src/heartwood/cli/_interactive.py` around lines 532 - 534,
Update the specialist/task-heading fallback expression in the interactive CLI to
match the consolidated Notebook and WebUI fallback order, reusing the same
precedence and source fields across all three locations. Change the expression
assigned to specialist while preserving its final fallback to action.tool_name.
🤖 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.

Nitpick comments:
In `@images/generic/scripts/local_model_stub.py`:
- Around line 252-256: Update the result-status logic near
_has_specialist_result so has_execution_result derives from the existing
has_specialist_result value instead of repeating the "specialist review
complete:" check over tool_results. Remove the duplicated json.dumps-based
condition while preserving the intended execution-result behavior.

In `@packages/cli/src/heartwood/cli/_interactive.py`:
- Around line 532-534: Update the specialist/task-heading fallback expression in
the interactive CLI to match the consolidated Notebook and WebUI fallback order,
reusing the same precedence and source fields across all three locations. Change
the expression assigned to specialist while preserving its final fallback to
action.tool_name.

In `@packages/gateway/tests/test_openhands_persistence.py`:
- Around line 143-151: Extend the persistence assertions for the minimized
errored observation written by store.write to verify that its task_id and
subagent lifecycle fields remain present in persisted. Keep the existing secret,
path, and error-message assertions unchanged, and assert the expected field
values from the event fixture.

In `@packages/gateway/tests/test_specialist_task.py`:
- Around line 24-45: Add a test covering all three rejection paths in
_validated_roles: empty specialist input, incomplete metadata, and duplicate
specialist_id values. Assert each raises ValueError with the expected message,
using a valid SpecialistToolRole duplicated for the uniqueness case.

In `@packages/notebook/src/heartwood/notebook/_widgets.py`:
- Around line 99-114: Update the capability label formatting in the action
display loop around action.details.capability to match the wording and
formatting used by the WebUI and CLI implementations; keep the existing
task-only conditional and capability presence checks unchanged.
- Around line 127-129: Align the task-heading fallback expression assigned to
label with the canonical fallback order used by the CLI and WebUI. Update the
ordering of details.role_label, details.description, details.subagent_type, and
action.summary consistently, preserving the existing values and fallback
behavior.

In `@packages/webui/src/components/ConversationWorkspace.tsx`:
- Around line 767-798: Update the capability label rendering in
ConversationWorkspace around specialist.capability to reuse the shared
capability-to-display-label mapping used by the CLI and Notebook, instead of
hardcoding “Advisory review” and “Project actions.” Preserve the existing
conditional rendering and risk badge behavior.
- Around line 566-572: Align the task-heading fallback chain in
ConversationWorkspace with the canonical ordering used by the equivalent CLI and
Notebook implementations. Update the action.details.kind === "task" branch to
use the same field precedence, preserving the final action.summary fallback.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d9c9833-53ce-4cee-8673-ed734f0c9e29

📥 Commits

Reviewing files that changed from the base of the PR and between 12ee178 and 0d30785.

⛔ Files ignored due to path filters (9)
  • documentation/assets/screenshots/browser-action-review.png is excluded by !**/*.png
  • documentation/assets/screenshots/browser-action-settings.png is excluded by !**/*.png
  • documentation/assets/screenshots/browser-changes.png is excluded by !**/*.png
  • documentation/assets/screenshots/browser-conversation.png is excluded by !**/*.png
  • documentation/assets/screenshots/browser-files.png is excluded by !**/*.png
  • documentation/assets/screenshots/browser-specialists.png is excluded by !**/*.png
  • packages/webui/src/apiTypes.generated.ts is excluded by !**/*.generated.*
  • packages/webui/src/sessionProjection.generated.ts is excluded by !**/*.generated.*
  • packages/webui/src/sessionProjectionSchema.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (37)
  • agents/verified/analysis-implementer.md
  • documentation/architecture/system.md
  • documentation/reference/glossary.md
  • documentation/use/browser.md
  • documentation/use/specialists.md
  • images/generic/scripts/local_model_stub.py
  • images/generic/scripts/offline_stack_smoke.sh
  • images/generic/scripts/terra_jupyter_demo_smoke.py
  • packages/cli/src/heartwood/cli/_interactive.py
  • packages/cli/tests/test_interactive.py
  • packages/compliance/tests/test_container_assets.py
  • packages/core-adapter/src/heartwood/core_adapter/_facade.py
  • packages/core-adapter/src/heartwood/core_adapter/_service.py
  • packages/gateway/src/heartwood/gateway/_action_presentation.py
  • packages/gateway/src/heartwood/gateway/_openhands_persistence.py
  • packages/gateway/src/heartwood/gateway/_openhands_sdk.py
  • packages/gateway/src/heartwood/gateway/_session_projection.py
  • packages/gateway/src/heartwood/gateway/_specialist_task.py
  • packages/gateway/src/heartwood/gateway/_specialists.py
  • packages/gateway/tests/test_action_settings.py
  • packages/gateway/tests/test_openhands_persistence.py
  • packages/gateway/tests/test_openhands_sdk.py
  • packages/gateway/tests/test_session_projection.py
  • packages/gateway/tests/test_specialist_task.py
  • packages/gateway/tests/test_specialists.py
  • packages/notebook/src/heartwood/notebook/_widgets.py
  • packages/notebook/tests/test_notebook.py
  • packages/schemas/src/heartwood/schemas/_api.py
  • packages/schemas/tests/test_api_contracts.py
  • packages/webui/scripts/smoke-reference-analysis.cjs
  • packages/webui/src/App.test.tsx
  • packages/webui/src/actionPresentation.test.ts
  • packages/webui/src/client.test.ts
  • packages/webui/src/components/ConversationWorkspace.tsx
  • packages/webui/src/components/UtilitySheet.tsx
  • packages/webui/src/e2e/app.spec.ts
  • packages/webui/src/styles.css
🚧 Files skipped from review as they are similar to previous changes (14)
  • packages/webui/src/client.test.ts
  • packages/core-adapter/src/heartwood/core_adapter/_service.py
  • packages/schemas/tests/test_api_contracts.py
  • packages/webui/src/e2e/app.spec.ts
  • documentation/use/specialists.md
  • documentation/reference/glossary.md
  • agents/verified/analysis-implementer.md
  • packages/webui/src/components/UtilitySheet.tsx
  • packages/webui/src/styles.css
  • packages/schemas/src/heartwood/schemas/_api.py
  • packages/webui/scripts/smoke-reference-analysis.cjs
  • packages/webui/src/App.test.tsx
  • packages/gateway/src/heartwood/gateway/_specialists.py
  • documentation/architecture/system.md

Copilot AI review requested due to automatic review settings August 3, 2026 18:16

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@PSchmiedmayer
PSchmiedmayer merged commit 1f8e959 into main Aug 3, 2026
25 checks passed
@PSchmiedmayer
PSchmiedmayer deleted the feature/specialized-research-agents branch August 3, 2026 18:56
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Heartwood Aug 3, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Aug 7, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Add Bounded Specialized Agents for Research Workflows

2 participants