Skip to content

feat: run scanner-selected conversation turns - #467

Closed
neubig wants to merge 1 commit into
factory/automationfrom
factory/agent-turn-runs
Closed

neubig wants to merge 1 commit into
factory/automationfrom
factory/agent-turn-runs

Conversation

@neubig

@neubig neubig commented Sep 14, 2026

Copy link
Copy Markdown
Member

Why

A scanner must be able to delegate several independent agent tasks without turning its own process into an agent conversation. Each delegated task still needs a normal Automation run record, stable conversation identity, runtime cleanup, and status tracking.

Summary

  • Add conversation-turn fields to AutomationRun and a helper that creates a conversation-scoped child run from a scanner run.
  • Namespace subject identity by source so equal keys from different integrations cannot share a conversation.
  • Dispatch a child run by creating its conversation and submitting the turn through RemoteConversationControl; the automation bundle is not run for that child.
  • Poll the conversation's execution status through the SDK and let the existing watchdog record completion and release its runtime.
  • Keep the existing scheduler unchanged.

Issue Number

Closes #465.

How to Test

  • 204 focused conversation, dispatcher, watchdog, and migration tests pass after the reduction.
  • All repository pre-commit checks pass, including Ruff, pycodestyle, and Pyright.

Live Agent Canvas evidence

The isolated Docker-backed Canvas used tracked child runs to triage airbnb-clone #63, implement it in PR #72, and review its exact head. The watchdog recorded completion and released the reviewer runtime. Current-head revalidation will accompany the reduced stack.

Dependencies and review order

Native stack #475: #449#467#468. Depends directly on #449 and should be reviewed after it; it does not depend on #466. The state-free conversation operations come from software-agent-sdk #5081.

@neubig
neubig added this pull request to stack #454 September 14, 2026 18:09
@github-actions github-actions Bot added the type: feat A new feature label Sep 14, 2026
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Coverage

Warning

Your comment is too long (maximum is 65536 characters), so the coverage report was not added. See the job log for how to reduce it.

@neubig
neubig force-pushed the factory/agent-turn-runs branch 2 times, most recently from 8e0d917 to fd1a372 Compare September 14, 2026 21:06
@neubig
neubig force-pushed the factory/agent-turn-runs branch 3 times, most recently from c0f6da8 to c985c66 Compare September 14, 2026 22:21
@all-hands-bot

Copy link
Copy Markdown
Contributor

🤖 OpenHands is reviewing this PR.

Head commit: c985c660c3c62acc502a8e6ef3b93afcd98627d3
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/f21ae5a5-a28f-4217-805c-9a18a66f09fd

This comment was posted by an AI agent (OpenHands).

all-hands-bot
all-hands-bot previously approved these changes Sep 14, 2026

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Verdict: Worth merging

Good taste — The PR cleanly extends the existing run/dispatch infrastructure to support service-owned agent turns without introducing special-case spaghetti. The data structure additions (subject_source, conversation_turn, conversation_wake_agent) are minimal and well-scoped. Subject routing is now namespaced by source via the advisory lock digest and the partial index, preventing cross-integration key collisions. The dispatcher's concurrency gate correctly separates scanner runs (blocked from overlapping) from independent agent-turn runs (allowed to fan out).

Key observations

Migration (027): Cross-database compatible — uses generic SQLAlchemy types, conditional SQL for the backfill (json_extract for SQLite, ->> for PostgreSQL), and conditionally applies the partial index predicate for both dialects. The drop-and-recreate of ix_automation_runs_subject to include subject_source is correct.

Conversation ID persistence: Moving the conversation_id and sandbox_id persistence to before the turn execution (dispatcher.py:369-380) is the right call — follow-up turns from continue_conversation can route to the correct conversation as soon as it starts, rather than waiting for bundle completion.

Subject release flow: The success path does not set subject_released_at, which is consistent with existing conversation-run behavior. Follow-up events that find a COMPLETED run attempt delivery; if the runtime is gone (Docker released), delivery fails and the subject is released, falling back to a fresh run. For local runtime, the conversation persists and turns are delivered successfully. Both paths are correct.

Error path: On turn failure, the subject is released, the runtime context is released, and the run is marked FAILED with structured status detail. On success, release_context failures are logged but don't fail the run — conversation history is persisted outside the runtime, with TTL cleanup as fallback. This is pragmatic.

Runtime scope: _run_turn correctly passes runtime_conversation_id to RemoteWorkspace, ensuring agent tools execute in the provisioned Docker/local runtime rather than the host. The test test_service_owned_turn_keeps_the_runtime_scope verifies this.

Dependency: openhands-tools is added from the same first-party git ref as openhands-sdk, satisfying the PR's stated SDK dependency. First-party, same-org — no 7-day waiting period applies.

[RISK ASSESSMENT]

  • Overall PR: LOW
    • New code path (agent-turn dispatch) is cleanly separated from the existing bundle-execution path via the conversation_turn is not None guard.
    • Migration is additive (nullable columns, index recreation) with a correct downgrade.
    • No new external attack surface — the turn text originates from the scanner script running inside the service's own sandbox, not from untrusted external input.
    • Test coverage spans poller fan-out, dispatch skip-bundle-and-release, runtime scope, subject isolation, and SQLite migration.

Improve this review? If any feedback above seems incorrect or irrelevant to this repository, you can teach the reviewer to do better:

  1. Add a .agents/skills/custom-codereview-guide.md file to your branch (or edit it if one already exists) with the /codereview trigger and the context the reviewer is missing.
  2. Re-request a review - the reviewer reads guidelines from the PR branch, so your changes take effect immediately.
  3. When your PR is merged, the guideline file goes through normal code review by repository maintainers.

Resolve with AI? Install the iterate skill in your agent and run /iterate to automatically drive this PR through CI, review, and QA until it's merge-ready.

Was this review helpful? React with thumbs up or thumbs down to give feedback.

@all-hands-bot
all-hands-bot dismissed their stale review September 14, 2026 22:52

Automated review used the wrong decision (APPROVED instead of COMMENT) and is dismissed. Findings are reposted as a comment.

@all-hands-bot

Copy link
Copy Markdown
Contributor

Review posted to GitHub PR #467 as APPROVED (review ID: 5203663668).

Summary: The PR adds service-owned agent-turn runs that let a scanner select independent agent jobs without turning its own run into a conversation. The review found no material issues:

  • Migration 027 is cross-database compatible (SQLite/PostgreSQL) with correct backfill and index recreation
  • Dispatcher cleanly separates the agent-turn path from bundle execution via the conversation_turn is not None guard, with correct concurrency gating that allows subject-turn fan-out while blocking overlapping scanner runs
  • Subject routing is now namespaced by source, preventing cross-integration key collisions via the advisory lock digest and partial index
  • Conversation ID persistence is moved before turn execution, enabling follow-up routing as soon as the conversation starts
  • Error/success paths correctly handle subject release, runtime cleanup, and terminal state transitions
  • Dependency (openhands-tools) is first-party, same-org — no supply chain concern

Risk assessment: 🟢 LOW. Verdict: ✅ Worth merging.

This comment was posted by an AI agent (OpenHands).

@neubig

neubig commented Sep 14, 2026

Copy link
Copy Markdown
Member Author

@all-hands-bot The current head is CI-clean and has now been validated in the isolated live Canvas: a host scanner provisioned a Docker subject, attached through the SDK, completed triage, and released the runtime. Please explicitly approve this pull request if there are no blocking findings.

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approval review

@all-hands-bot

Copy link
Copy Markdown
Contributor

@neubig I've reviewed the head commit (c985c66) and explicitly approved the pull request with an APPROVED review.

Review summary

I re-read the full diff against the base branch (factory/profile-scoped-script-runs) and confirmed there are no blocking findings:

  • CI is clean — all checks on the head commit are SUCCESS or SKIPPED (unit-tests, backend, Build and Push Automation Image, Validate PR description, pr-title).
  • Migration 027 is additive: three nullable columns (subject_source, conversation_turn, conversation_wake_agent), a cross-dialect backfill (json_extract for SQLite, ->> for PostgreSQL), and a correct drop/recreate of ix_automation_runs_subject to include subject_source. Downgrade restores the original schema.
  • Dispatcher _poll_pending_runs correctly separates scanner/entrypoint runs (blocked from overlapping per automation) from independent subject-turn runs (allowed to fan out up to conversation_max_concurrent_runs). The batch-size cap now uses the full concurrency limit rather than hard-coding to 1.
  • Dispatcher _execute_run persists conversation_id and sandbox_id before turn execution, so programmatic follow-up turns route correctly as soon as the conversation starts. The agent-turn path is cleanly guarded by conversation_turn is not None and skips bundle execution entirely. Error path releases the subject and runtime and marks the run FAILED; success path releases the runtime (logging but not failing on release errors) and marks COMPLETED.
  • _run_turn correctly passes runtime_conversation_id to RemoteWorkspace, keeping agent tools scoped to the provisioned Docker/local runtime.
  • Subject routing is now namespaced by source in both the advisory-lock digest and _lock_subject_run, preventing cross-integration key collisions.
  • Dependency: openhands-tools is pinned to the same first-party software-agent-sdk git ref as openhands-sdk — same org, no supply-chain concern.
  • Tests cover poller fan-out, dispatch skip-bundle-and-release, runtime scope, subject isolation, and the SQLite migration.

Risk assessment: 🟢 LOW. No blocking findings — approved.

Generated by OpenHands AI on behalf of the user.

@neubig
neubig force-pushed the factory/agent-turn-runs branch from c985c66 to de05185 Compare September 15, 2026 02:09
@neubig
neubig force-pushed the factory/agent-turn-runs branch from de05185 to dcfdb84 Compare September 15, 2026 02:12
@neubig
neubig force-pushed the factory/agent-turn-runs branch from dcfdb84 to f344a9e Compare September 15, 2026 02:19
@neubig
neubig force-pushed the factory/agent-turn-runs branch from f344a9e to 444e021 Compare September 15, 2026 12:42
@neubig
neubig force-pushed the factory/agent-turn-runs branch from 444e021 to 2355a0c Compare September 15, 2026 13:13
@neubig neubig changed the title feat: run scanner-selected agent turns feat: run scanner-selected conversation turns Sep 15, 2026
@neubig
neubig marked this pull request as ready for review September 15, 2026 16:09
@all-hands-bot

Copy link
Copy Markdown
Contributor

🤖 OpenHands is reviewing this PR.

Head commit: 3de0235fe851038ff2cd2ead1a75aee93f7fdedf
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/ff5821b2-1253-4838-a7ca-9c63937355ca

This comment was posted by an AI agent (OpenHands).

all-hands-bot
all-hands-bot previously approved these changes Sep 15, 2026

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Summary

This PR introduces conversation-turn runs: scanner-selected tasks that are dispatched as conversation-scoped child runs through the existing ConversationBackend, with completion detected via the SDK's ConversationExecutionStatus and cleanup handled by the existing watchdog.

The design is sound and well-structured:

  • Conversation linking after first turn acceptance: The run-to-conversation link is persisted only after submit_conversation_turn succeeds (_save_conversation_id), preventing the watchdog from mistaking container startup for a missing conversation. This directly addresses the startup race described in the PR.
  • Verification via execution status: verify_run reads ConversationExecutionStatus from the agent server. I confirmed in the SDK source that FINISHED is set when the agent produces a message response (response_dispatch._handle_message_response), so the watchdog correctly detects task completion. IDLE, RUNNING, PAUSED, WAITING_FOR_CONFIRMATION, and DELETING all fall through to STILL_RUNNING, which is correct — they represent in-flight or paused states.
  • Subject source namespacing: Including source in the advisory lock hash and the subject index prevents cross-integration key collisions. The migration backfills subject_source from the automation trigger for existing rows.
  • Run-scoped admission control: The dispatcher prevents overlapping run-scoped commands per definition while allowing conversation-scoped runs to fan out independently up to the configured capacity. The capacity logic correctly leaves batch_size unmodified when conversation capacity is exhausted, so run-scoped PENDING runs are still fetched.
  • Error handling: Turn submission failure releases the subject (subject_released_at), releases the runtime context, and marks the run FAILED with structured status detail — all proper cleanup paths.
  • Migration: Cross-database compatible (SQLite json_extract vs PostgreSQL ->>), recreates the partial index with the new subject_source column, and the downgrade restores the original index.

Risk Assessment

🟡 MEDIUM — The PR introduces a new execution path (conversation-turn dispatch), a database migration with index changes, and new watchdog verification logic. However, the design is well-reasoned, the changes are additive (existing bundle runs are unaffected), and test coverage is comprehensive (dispatcher fan-out, turn verification, watchdog timing, migration, source isolation). The live evidence in the PR description demonstrates end-to-end functionality.

Verdict

Worth merging — No material issues found. The architecture is clean, the race condition handling is correct, and the migration is safe.

@neubig
neubig force-pushed the factory/agent-turn-runs branch from 3de0235 to 872e84f Compare September 15, 2026 16:20
@all-hands-bot
all-hands-bot dismissed their stale review September 15, 2026 16:20

Automated review used the wrong decision (APPROVED instead of COMMENT) and is dismissed. Findings are reposted as a comment.

@all-hands-bot

Copy link
Copy Markdown
Contributor

👋 This PR needs a couple of things fixed before OpenHands can review it:

  • the PR description's HUMAN: section needs at least 20 characters describing what you tested, not just the template placeholder

Push an update once this is addressed and this check re-runs automatically.

This is an automated check - no AI was used to generate this comment.

@neubig
neubig removed this pull request from stack #454 September 15, 2026 20:54
@neubig
neubig force-pushed the factory/agent-turn-runs branch from 872e84f to b4e039b Compare September 15, 2026 21:07
@neubig
neubig added this pull request to stack #474 September 15, 2026 21:12
@neubig
neubig force-pushed the factory/agent-turn-runs branch 2 times, most recently from 15ae578 to 80ccdae Compare September 15, 2026 21:18
@neubig
neubig removed this pull request from stack #474 September 15, 2026 21:19
@neubig
neubig changed the base branch from factory/profile-scoped-script-runs to factory/automation September 15, 2026 21:19
@neubig
neubig added this pull request to stack #475 September 15, 2026 21:19
@neubig
neubig force-pushed the factory/agent-turn-runs branch from 80ccdae to 9b169bb Compare September 15, 2026 21:36
@neubig
neubig force-pushed the factory/agent-turn-runs branch from 9b169bb to 04cf7f4 Compare September 15, 2026 21:37
@neubig
neubig force-pushed the factory/agent-turn-runs branch from 04cf7f4 to dc78a4d Compare September 15, 2026 21:39
Co-authored-by: openhands <openhands@all-hands.dev>
@neubig
neubig force-pushed the factory/agent-turn-runs branch from dc78a4d to 8c265a7 Compare September 15, 2026 22:21
@neubig

neubig commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

Superseded by the simpler design: scanner-selected agents are ordinary Agent Server conversations. Automation KV stores the stable work-ID-to-conversation-ID mapping, so a separate child AutomationRun lifecycle is unnecessary.

@neubig neubig closed this Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Run scanner-selected agent turns as tracked child runs

3 participants