Skip to content

feat: dispatch automation bundles through SDK conversation runtimes - #449

Draft
neubig wants to merge 1 commit into
mainfrom
factory/automation
Draft

neubig wants to merge 1 commit into
mainfrom
factory/automation

Conversation

@neubig

@neubig neubig commented Sep 12, 2026

Copy link
Copy Markdown
Member

Why

An automation run must state whether it executes a host-side script or an agent job. A saved agent profile supplies policy and credentials; its presence should not implicitly choose a backend. Making the execution mode explicit also lets deterministic scanners continue running while the bounded agent-conversation slots are occupied.

Summary

  • Add an explicit script or agent execution mode to automation definitions and queued-run snapshots. Existing definitions migrate to script.
  • Use the conversation backend only for agent runs. The configured deployment profile is required by that backend in this parent PR, but it no longer selects the backend; per-automation profile selection follows in feat: select agent profiles on automation definitions #453.
  • Count only agent conversations against local conversation capacity. Host-side scripts remain dispatchable, while overlapping script runs for the same automation are prevented.
  • Reuse the existing scheduler, run records, backend interface, SDK workspace/conversation APIs, upload helpers, watchdog verification, and resource cleanup.

Issue Number

Closes #448. Closes #450.

How to Test

The backend-selection regression covers both execution modes and makes each backend construction explicit. Focused conversation tests pass locally, including the profile-backed path that previously failed CI. Existing live evidence below demonstrates the same uploaded bundle in local and Docker conversation runtimes. Full repository CI is running on the current head.

Dependencies and review order

Native stack #454: review #449 before #453. Merged SDK #4966 and #5010 provide the runtime and explicit conversation APIs. The temporary SDK source pin must be replaced by a release before merge. Docker deployments additionally use SDK #3403.

Live Agent Canvas evidence

After moving subject-ID support into #449, the assembled #453 production tree remains 9de630f76614a6a26ce8a9088e7907bab1865f54, identical to the published live recording. Tests additionally verify persisted subject-derived conversation metadata. The two subject-keyed backend cases fail before the parent fix and pass afterward.

Current explicit API: live local/Docker demonstration · Exact revisions, bundle, results and limits. The identical uploaded bundle completed a real agent task with RemoteConversation.create / .attach in both runtimes, exited 0, and produced the expected file in Canvas. The report retains the initial request-validation failure and successful rerun after the SDK serialization fix. All private workers and services were released afterward. This validates the explicit API migration; earlier admission, queued-profile and credential-scope recordings remain separate.

Earlier SDK migration: 24-second local/Docker demonstration · Exact revisions, executed bundle, results and limits. Both runs completed a real DeepSeek task, wrote and independently read back the expected file, and appeared as Successful in Canvas. The identical bundle and SDK source hashes were verified in both workers; all private runtimes were released afterward.

The current recording validates dispatch/upload/execution parity; it does not repeat the earlier concurrency/admission or credential-scope matrix. Earlier evidence follows:

Animated recording · Scenario, exact revisions and limits · Allowlisted observations. Direct integrated enhancement demonstration with a real agent and synthetic secrets; the report distinguishes the owning PR from companion SDK/Automation/Canvas changes.

Merge and release prerequisites

Must be released first: merged software-agent-sdk#4966 and open software-agent-sdk#5010, which must also merge. Replace the temporary SDK source pin with the released dependency before merging this PR. Docker deployments additionally need software-agent-sdk#3403; profile-scoped factory credentials need software-agent-sdk#4931 / software-agent-sdk#5017. These cross-repository prerequisites are not represented by native stack #454.

Final split audit

The SDK source pin now follows #5010 current head e9392e7cf35fcb011acf2eca825a80f9a22774ea; this updates only dependency identity after that branch refresh. The execution, attachment, and runtime-identity implementation remains unchanged from the published explicit-API live proof. The parent’s 66 focused conversation/backend tests pass against that exact SDK head; the standalone dispatch proof above remains applicable. The released SDK dependency is still a merge prerequisite.

@github-actions

github-actions Bot commented Sep 12, 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.

@all-hands-bot

Copy link
Copy Markdown
Contributor

🤖 OpenHands is reviewing this PR.

Head commit: f8ca7062395158dc15b080cb879975e13afd112b
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/29802e35-69c2-4f34-82c0-34cc4b3106b2

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

all-hands-bot
all-hands-bot previously approved these changes Sep 13, 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.

Review Summary

This PR adds an opt-in Docker conversation backend for local automation runs. Each run gets its own agent-server conversation, a scoped runtime credential, and no access to the host control key or shared callback key. Completion is verified via scoped bash history by the watchdog, and concurrency is limited by docker_max_concurrent_runs.

The design is sound: credential isolation is correct (the bundle receives only the inner SESSION_API_KEY, not the outer host key or callback key), the concurrency limiter prevents overload, and the watchdog's expanded stale detection is necessary since the bundle lacks the callback credential. Tests exercise real code paths with MockTransport rather than just asserting mocked calls.

Non-blocking observations (not preventing approval):

  1. Cancel path lacks error handling for runtime release (router.py:890): cleanup_after_verification is called without a try/except in the cancel endpoint. If the Docker runtime release fails, the cancel endpoint returns 500 even though the run was already marked CANCELLED. The watchdog path wraps this call in try/except - the cancel path should do the same for consistency.

  2. Hardcoded AGENT_SERVER_URL (backends/docker.py:65): The Docker backend hardcodes http://127.0.0.1:8000 instead of using the parent class's sandbox_agent_server_url override. This is fine for the stated single-process target, but will surprise anyone running the agent server on a non-default port. Consider falling back to self.sandbox_agent_server_url or "http://127.0.0.1:8000".

  3. Hardcoded max_iterations: 160 (backends/docker.py:44): Not configurable per-automation. Acceptable for an initial opt-in feature, but worth a follow-up to make it overridable.

None of these are blocking - the core logic, security model, and test coverage are solid.

[RISK ASSESSMENT]

  • Overall PR: LOW
  • Opt-in only: no change to existing Cloud or shared local modes unless AUTOMATION_DOCKER_AGENT_PROFILE is explicitly set.
  • Credential isolation verified: host key and callback key are not forwarded to the bundle.
  • Concurrency control prevents resource exhaustion.

VERDICT: Worth merging

KEY INSIGHT: The credential isolation model - provisioning a scoped runtime key via /runtime/credentials and withholding the host control key - is the correct security boundary for running untrusted bundle code in isolated Docker conversations.

Comment thread openhands/automation/router.py Outdated
Comment thread openhands/automation/backends/docker.py Outdated
@all-hands-bot

Copy link
Copy Markdown
Contributor

Posted an APPROVED review on PR #449 (commit f8ca706) with 2 inline comments and a full review body.

Verdict: Worth merging (APPROVED)
Risk: LOW

Summary of findings:

  • The PR adds an opt-in Docker conversation backend for local automation runs with correct credential isolation (bundle receives only scoped runtime key, not host control key or callback key)
  • Concurrency control via docker_max_concurrent_runs prevents overload
  • Watchdog expanded stale detection is necessary since the bundle lacks the callback credential
  • Tests exercise real code paths with MockTransport

Non-blocking observations posted as inline comments:

  1. router.py:890 — Cancel path lacks try/except around cleanup_after_verification (watchdog path has it, cancel path doesn't)
  2. backends/docker.py:65 — Hardcoded AGENT_SERVER_URL instead of using sandbox_agent_server_url from parent class

Review URL: #449 (review)

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

@all-hands-bot
all-hands-bot dismissed their stale review September 13, 2026 01:51

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

@neubig
neubig removed this pull request from stack #452 September 13, 2026 04:36
@neubig neubig changed the title feat: dispatch local automation bundles in Docker conversations feat: dispatch automation bundles through SDK conversation runtimes Sep 13, 2026
@neubig
neubig added this pull request to stack #454 September 13, 2026 04:40
@all-hands-bot

Copy link
Copy Markdown
Contributor

🤖 OpenHands is reviewing this PR.

Head commit: dcf1c572e02c0c14cc3dc643d9d59ca7bb284c73
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/e7e9287b-8899-47be-a26a-0f4c6efe90cc

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

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

Review Summary

This PR consolidates the SDK conversation dispatch into a single ConversationAgentServerBackend that works across both local and Docker workspace runtimes. The design is sound: credential isolation is correct (the bundle receives only the scoped runtime key, never the host key or callback key), the concurrency limiter prevents overload and per-automation overlap, and the watchdog's expanded stale detection covers the missing-callback case.

The previous review's two findings (cancel path error handling, hardcoded URL) are both addressed in this commit — cancel now wraps cleanup in try/except, and the Docker backend is consolidated into the conversation backend with sandbox_agent_server_url fallback.

Non-blocking observations (not preventing approval):

  1. api_prefix property constructs a client on every access (conversation.py:32-37): The property instantiates a new AsyncAgentServerClient without an http_client each time it's read. It's called in both get_execution_context (line 68) and verify_run (line 116). Since it only reads a computed string, this could be cached on the instance after first resolution to avoid repeated object creation.

  2. Missing AUTOMATION_CALLBACK_API_KEY is by design but has a latency cost: build_env_vars deliberately omits the callback key (the README confirms: "never the outer server key or shared callback key"). This means all completions go through the watchdog, not the callback path. The watchdog's expanded stale detection (bash_command_id.isnot(None) | timeout_at < now) handles this, but every running conversation-mode run will be verified on each watchdog scan. With conversation_max_concurrent_runs defaulting to 2 this is bounded, but worth noting that completion latency is tied to the watchdog scan interval rather than being immediate.

  3. SDK git pin must be replaced before merge: The PR description explicitly calls this out. pyproject.toml pins openhands-sdk to git commit 79021c687... instead of a released version. This is a known pre-merge requirement, not a code issue.

[RISK ASSESSMENT]

  • Overall PR: 🟢 LOW
  • Opt-in only: no change to existing Cloud or shared local modes unless AUTOMATION_AGENT_PROFILE is explicitly set.
  • Credential isolation verified: host key and callback key are not forwarded to the bundle in Docker mode.
  • Concurrency control prevents resource exhaustion (default max 2 concurrent runs).
  • Cancel path correctly handles cleanup failures without affecting the state transition.
  • The watchdog expansion is bounded by the concurrency limiter.

VERDICT: ✅ Worth merging — replace the SDK git pin with the released version as the PR description states.

KEY INSIGHT: The unified ConversationAgentServerBackend cleanly replaces the previous Docker-specific backend by discovering the runtime kind from server capabilities and branching only on credential provisioning and workspace path, keeping the execution contract identical for the bundle.


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 (e.g., "Security concerns about X do not apply here because Y"). See the customization docs for the required frontmatter format.
  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 👍 or 👎 to give feedback.

Comment thread openhands/automation/backends/conversation.py Outdated
@all-hands-bot

Copy link
Copy Markdown
Contributor

🤖 OpenHands is reviewing this PR.

Head commit: 306bbc607da0d9d90113fda04a89c2d208b69e64
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/ec32baa9-a06c-4a73-8f97-aebe3837a326

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

all-hands-bot
all-hands-bot previously approved these changes Sep 13, 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.

Review Summary

🟢 Good taste — The conversation backend cleanly unifies local and Docker workspace execution behind one bundle-facing contract. The design reuses the existing backend abstraction, dispatcher pipeline, and verification helpers without duplicating logic. Runtime credential scoping (host key for local, inner session key for Docker) is correct and well-tested.

Key findings

No blocking issues found. The implementation is sound:

  • ConversationAgentServerBackend properly resolves the runtime kind once and caches it, provisions scoped credentials per mode, and releases Docker runtimes while preserving local history.
  • The dispatcher's concurrency limiting (min(batch_size, 1, max_concurrent - active)) correctly bounds admission to one new run per poll cycle while respecting the global cap.
  • Cancellation correctly commits the state transition before attempting runtime cleanup, and cleanup failures are caught and logged without reverting the cancellation — the right trade-off.
  • The watchdog's expanded stale condition (runs with bash_command_id but not yet timed out) is a deliberate design choice: without a sandbox to observe, the only way to detect completion is to query the agent server. This adds periodic verification load but is the correct approach for the conversation backend.
  • Env var layering is correct: the dispatcher's AUTOMATION_CONVERSATION_ID override for subject_key runs takes precedence over the backend's default, and SESSION_API_KEY from build_env_vars is not clobbered by the sandbox-id path (since ctx.sandbox_id is None for conversation runs).

Non-blocking observations

  1. cleanup_after_verification uses a default-timeout httpx.AsyncClient() (conversation.py:120). The default httpx timeout is 5s; Docker runtime release could exceed that. Since errors are caught upstream, this won't crash but could silently leave Docker resources unreleased. Consider passing an explicit timeout (e.g., 30s) consistent with verify_run_on_agent_server's 60s client.

  2. The api_prefix property (conversation.py:31-37) constructs a throwaway AsyncAgentServerClient on each access. This is called from get_execution_context, verify_run, and cleanup_after_verification. If api_prefix is a locally computed string (as the test suggests), this is wasteful but harmless. If it ever involves a network call, it would need to be async.

  3. Temporary SDK git pin — The PR description explicitly states this must be replaced with the SDK release before merging. This is a known pre-merge gate, not a code defect.

Test coverage

Tests are well-structured and exercise real code paths through mock transports rather than mocking the unit under test:

  • test_conversation_backend.py validates both runtime kinds end-to-end (context creation, env vars, scoped execution, release).
  • test_cancel_run.py covers both cleanup success and failure paths.
  • test_local_mode.py and test_execution.py properly test through the SDK's HTTP transport layer.

Risk Assessment

[RISK ASSESSMENT]

  • Overall PR: 🟡 MEDIUM
  • New execution backend with dispatcher/watchdog/router changes across 17 files. Well-tested with good error handling boundaries. The temporary SDK git pin and the expanded watchdog scanning scope are the main risk vectors, both acknowledged in the PR description. No security issues found — credential scoping is correct (Docker gets inner session key only, never the host key).

VERDICT:Worth merging — Core logic is sound, credential isolation is correct, and error handling is appropriately defensive. Replace the SDK git pin with the release before merging as noted in the PR description.

KEY INSIGHT: The conversation backend's inheritance from LocalAgentServerBackend is the right abstraction — it reuses workspace resolution and env var structure while overriding only the runtime-aware methods, keeping the surface area minimal.


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 (e.g., "Security concerns about X do not apply here because Y"). See the customization docs for the required frontmatter format.
  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 👍 or 👎 to give feedback.

Comment thread openhands/automation/backends/conversation.py
@all-hands-bot
all-hands-bot dismissed their stale review September 13, 2026 13:00

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

Code review for PR #449 (feat: dispatch automation bundles through SDK conversation runtimes) has been posted to GitHub as APPROVED.

Review verdict: ✅ Worth merging (🟡 MEDIUM risk)

Key findings:

  • No blocking issues found
  • The conversation backend cleanly unifies local and Docker workspace execution behind one contract
  • Credential scoping is correct (Docker gets inner session key only, never the host key)
  • Concurrency limiting, cancellation error handling, and watchdog stale detection are all sound
  • One non-blocking inline comment: cleanup_after_verification uses a default 5s httpx timeout that could silently fail Docker runtime release
  • Temporary SDK git pin is explicitly documented as a pre-merge gate

Review URL: #449 (review)

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

@neubig
neubig requested review from all-hands-bot and removed request for all-hands-bot September 13, 2026 13:08
@all-hands-bot

Copy link
Copy Markdown
Contributor

Code review for PR #449 (feat: dispatch automation bundles through SDK conversation runtimes) has been posted to GitHub as APPROVED.

Verdict: ✅ Worth merging — 🟢 LOW risk

Key findings:

  • No blocking issues, critical bugs, or security problems found
  • Credential isolation is sound: Docker workers receive only inner session keys, never host keys or callback credentials
  • Concurrency limiting is correct: dispatcher caps admission to 1 per poll cycle, excludes overlapping automations
  • Conversation ID derivation is consistent across creation, worker context, verification, and cleanup
  • Outcome/turn resolution correctly avoids provisioning new conversations (real bug fix via isinstance(backend, LocalAgentServerBackend) check instead of is_local_mode)
  • Watchdog polling integration correctly extends to profile-backed runs
  • 72+ tests covering local/Docker routing, credential scoping, upload rejection, conversation ID persistence, cancellation, and runtime release

Merge prerequisite noted: The temporary openhands-sdk git pin must be replaced with the released SDK dependency before merging (SDK PRs #4966, #5010 must be released first).

Per the repo's custom code review guide, the review was submitted as APPROVED (not COMMENTED) since the verdict is "Worth merging" with LOW risk and no blocking issues.

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

@neubig

neubig commented Sep 14, 2026

Copy link
Copy Markdown
Member Author

@all-hands-bot please review the current head after the SDK pin refresh and explicitly approve it if there are no material 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.

Review: feat: dispatch automation bundles through SDK conversation runtimes

Head commit reviewed: 9b4ede3ad01beffd8e247950658b10c64a768772

Scope of this review

Reviewed the current head after the SDK pin refresh (e238a229b4ede3). The only change since the last reviewed commit is the SDK git pin update in pyproject.toml and uv.lock (de5bc17ee9392e7c) — no application code changes. I re-reviewed the full PR diff against main and ran the test suite.

Verdict: ✅ APPROVED — no material findings

What the pin refresh changes

The commit chore: refresh SDK integration pin updates only:

  • pyproject.toml: SDK git pin de5bc17e…e9392e7c…
  • uv.lock: corresponding lockfile update

No source files were touched. The new SDK revision (version 1.47.0) preserves the typed APIs this PR depends on:

  • AsyncRemoteWorkspace.file_upload()FileOperationResult (with .success / .error)
  • AsyncRemoteWorkspace.execute_command()CommandResult (with .exit_code / .stdout / .stderr)
  • AsyncRemoteWorkspace.start_command()str (command ID)
  • AsyncRemoteWorkspace.get_command_output()dict[str, Any] | None
  • AsyncRemoteWorkspace.get_runtime_session_key()str
  • AsyncRemoteWorkspace.release_runtime()None
  • RemoteConversation.create() / .attach() / .close() / .set_title() / .send_message() / .run()

All return types match the usage in conversation.py, execution.py, agent_server.py, conversation_outcome.py, and conversation_turn.py.

Full PR assessment (unchanged from prior reviews)

Architecture: The ConversationAgentServerBackend cleanly extends LocalAgentServerBackend, providing one bundle-facing contract across local and Docker runtimes. Runtime kind is discovered from server capabilities and drives all behavior differences through a single _runtime_kind field.

Credential isolation: Sound. Docker workers receive only the inner session key (get_runtime_session_key()), never the host server key. build_env_vars() raises RuntimeError if runtime_api_key is empty, preventing accidental credential leakage. The callback URL conditional correctly suppresses AUTOMATION_CALLBACK_URL for profile-backed runs.

Concurrency control: Correct. min(batch_size, 1, max_concurrent - active) with not_in(active) ensures at most one in-flight run per automation and respects the global cap. Returns [] when at capacity.

Conversation ID consistency: The subject-derived conversation_id_for is used identically across backend creation, worker context, verification, and cleanup. Non-subject runs fall back to run.id.

Cancel path: The router.py cancel path now wraps cleanup_after_verification in try/except, logging cleanup failures without blocking the cancellation — matching the watchdog path's behavior.

Outcome/turn resolution: fetch_latest_finish_tool_response_for_run and _resolve_agent_server correctly check isinstance(backend, LocalAgentServerBackend) and call get_api_key() directly instead of get_execution_context() — avoiding provisioning a new conversation just to read state or send a turn.

Testing

  • 6/6 test_conversation_backend.py tests pass (local/docker × subject/no-subject + backend selection + cleanup timeout)
  • 153 passed, 0 failed in PR-relevant test files (66 errors are all Docker testcontainer setup failures — no Docker daemon in this environment; these are environment limitations, not code defects)
  • CI on GitHub: 7 successful, 2 skipped, 0 failing
  • The sdk_http_transport fixture correctly intercepts SDK-owned HTTP pools via MockTransport

Merge prerequisite (acknowledged, not a code defect)

The openhands-sdk git pin (e9392e7c…) must be replaced with the released SDK dependency before merging. SDK PRs #4966 and #5010 must be released first. Docker deployments additionally need SDK #3403. These are cross-repository prerequisites documented in the PR description, not issues with this PR's code.

Risk Assessment

🟢 LOW — The pin refresh is a pure dependency update with no code changes. The application code is sound, well-tested, and all CI checks pass. The only merge gate is the SDK release dependency, which the PR explicitly documents.

Generated by OpenHands AI on behalf of the user.

@all-hands-bot

Copy link
Copy Markdown
Contributor

I've reviewed the current head (9b4ede3) after the SDK pin refresh and submitted an APPROVED review.

Summary

The only change since the last reviewed commit (e238a22) is the SDK git pin update in pyproject.toml and uv.lock (de5bc17ee9392e7c) — no application code changes. I verified that the new SDK revision preserves all typed APIs this PR depends on (file_upload, execute_command, start_command, get_command_output, get_runtime_session_key, release_runtime, RemoteConversation.create/attach/close), and all return types match the usage in the automation code.

No material findings. The PR is approved.

Key points confirmed:

  • Credential isolation is sound — Docker workers receive only the scoped session key, never the host key
  • Concurrency control is correct — at most one in-flight run per automation, respecting the global cap
  • Conversation ID consistency — subject-derived ID used identically across creation, worker context, verification, and cleanup
  • Cancel path — wraps cleanup_after_verification in try/except, matching the watchdog path
  • Tests — 6/6 conversation backend tests pass; CI is green (7 successful, 0 failing)
  • SDK pin — still a git pin, must be replaced with the released dependency before merging (documented prerequisite, not a code defect)

Generated by OpenHands AI on behalf of the user.

@all-hands-bot

Copy link
Copy Markdown
Contributor

🤖 OpenHands is reviewing this PR.

Head commit: a7c1ef35448c9d723f15d0c25435e1ddfa2588b5
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/eeef867a-1581-4dde-8ebe-0848f32b898f

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.

Verdict: ✅ APPROVED

🟢 Good taste — The PR cleanly introduces a unified ConversationAgentServerBackend that inherits from LocalAgentServerBackend, providing one bundle-facing contract across local and Docker workspace runtimes. The design eliminates special cases rather than adding conditional branches throughout the dispatch pipeline.

Key strengths

  1. Credential isolation is sound. Docker workers receive only the inner session key (get_runtime_session_key()), never the host server key or shared callback credential. The build_env_vars() guard that raises RuntimeError when runtime_api_key is empty prevents accidental credential leakage. The callback URL conditional in the dispatcher correctly suppresses AUTOMATION_CALLBACK_URL for profile-backed runs.

  2. Concurrency limiting is correct. _poll_pending_runs counts active RUNNING agent-mode runs, caps admission to 1 per poll cycle via min(batch_size, 1, agent_capacity), and excludes automations with already-running runs via not_in(active). Script-mode runs remain runnable when agent capacity is exhausted.

  3. Conversation ID consistency. The subject-derived conversation_id_for is used identically across backend creation, worker context, verification, and cleanup. The dispatcher persists the same ID the worker uses. Non-subject runs correctly fall back to run.id.

  4. Outcome/turn resolution avoids side effects. fetch_latest_finish_tool_response_for_run and _resolve_agent_server now check isinstance(backend, LocalAgentServerBackend) and call get_api_key() directly instead of get_execution_context() — correctly avoiding provisioning a new conversation just to read state or send a turn. This is a real bug fix since ConversationAgentServerBackend.get_execution_context creates a conversation.

  5. Watchdog polling integration. mark_stale_runs now picks up agent-mode runs with bash_command_id on every scan, not just after timeout. _should_cleanup_sandbox_after_terminal correctly triggers Docker runtime release even without sandbox_id.

  6. Cancel path. The cancel endpoint wraps cleanup_after_verification in try/except, logging cleanup failures without blocking the cancellation — matching the watchdog path's behavior.

Non-blocking observation

Deferred cleanup skips runtime release for agent-mode runs without sandbox_id (watchdog.py:404-415): When sandbox_cleanup_delay_seconds > 0, the watchdog calls _defer_sandbox_cleanup, which returns early if sandbox_id is None (line 197-198). For agent-mode Docker runs — which have no sandbox_id but do need release_runtime() — this means the runtime release is skipped entirely. With the default sandbox_cleanup_delay_seconds=0 the immediate cleanup_after_verification path runs correctly, so this only affects non-default configurations. Worth a follow-up to route agent-mode cleanup through the immediate path regardless of the delay setting, or to handle runtime release inside _defer_sandbox_cleanup.

Merge prerequisite (acknowledged, not a code defect)

The openhands-sdk git pin (e9392e7c…) must be replaced with the released SDK dependency before merging. SDK PRs #4966 and #5010 must be released first. These are cross-repository prerequisites documented in the PR description.

Risk Assessment

🟢 LOW — The change is scoped to local mode (profile-backed); cloud sandbox dispatch is unaffected. Credential isolation is correctly implemented and tested. Concurrency bounds are enforced at the dispatcher level. Comprehensive test coverage covers local/Docker routing, credential scoping, conversation ID persistence, cancellation cleanup, and runtime release lifecycle.

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 👍 or 👎 to give feedback.

@all-hands-bot

Copy link
Copy Markdown
Contributor

The review was successfully posted to GitHub as an APPROVED review (ID: 5204684165) against commit a7c1ef35448c.

The review concluded with a ✅ Worth merging verdict and 🟢 LOW risk assessment, with one non-blocking observation about deferred cleanup potentially skipping runtime release for agent-mode Docker runs when sandbox_cleanup_delay_seconds > 0 (an edge case that only affects non-default configurations).

GITHUB_REVIEW_POSTED

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

@all-hands-bot
all-hands-bot dismissed their stale review September 15, 2026 01: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

🤖 OpenHands is reviewing this PR.

Head commit: 3757609c049571bc96d31fe2fa2b7f9e3b3ab5b2
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/09622e86-b41b-4422-b41e-d30d669cfad4

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

@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 a ConversationAgentServerBackend that provisions SDK conversations (via RemoteConversation.create / AsyncRemoteWorkspace) for execution_mode: "agent" automations, with bounded concurrency, watchdog-based completion polling, and scoped credential handling. The migration, schema, dispatcher capacity logic, and git-sync serialization are well-integrated.

The core design is sound: conversation IDs are deterministically derived from subject keys, the callback-credential gating prevents leaking the shared service key to profile-scoped workers, and the dispatcher capacity logic correctly prevents overlap for one automation while keeping script runs unblocked.

Findings

1. Docker runtime resource leak when sandbox_cleanup_delay_seconds > 0

_should_cleanup_sandbox_after_terminal now returns True for agent-mode runs even when sandbox_id is None. When sandbox_cleanup_delay_seconds > 0, the watchdog takes the _defer_sandbox_cleanup branch, which returns immediately for runs without a sandbox_id (line 193: if not sandbox_id: return) without stamping sandbox_cleanup_due_at. The janitor (cleanup_due_sandboxes) then never picks up these runs, and even if it did, it guards cleanup on if run.sandbox_id: (line 1031). The result: Docker agent-mode runtimes that complete or time out are never released when a cleanup delay is configured.

With the default (sandbox_cleanup_delay_seconds = 0) the immediate cleanup_after_verification path works correctly. This only affects deployments that explicitly set a delay, but it is a silent resource leak in that configuration. Consider either skipping the deferred path for agent-mode runs (calling cleanup_after_verification directly) or teaching _defer_sandbox_cleanup / cleanup_due_sandboxes to handle runs whose cleanup target is a runtime rather than a sandbox.

2. _fetch_latest_finish_tool_response loads entire conversation event history into memory

The previous implementation used a paginated search endpoint (limit=100, newest-first). The new SDK-based implementation calls conversation.state.events and dumps every event to JSON via model_dump(mode="json") before scanning for the latest finish tool response. For long-running agent conversations (hundreds of iterations), this loads the full event list into memory on every call. If the SDK does not lazy-load or paginate state.events, this is a potential memory and latency regression for outcome lookups.

3. SDK git pin — merge prerequisite

openhands-sdk is pinned to an immutable git commit (e9392e7c...). This is explicitly documented as temporary and must be replaced with the released SDK dependency before merging. Not a blocking review issue, but a release gate.

Risk Assessment

MEDIUM - The deferred cleanup gap (Finding 1) is a real resource leak in a non-default but supported configuration. The event-loading concern (Finding 2) is a performance regression that may not manifest until conversations grow large. Neither blocks the default deployment path, which works correctly.

Verdict

Worth merging - The core implementation is well-structured, the security boundary (callback credential gating) is correct, and tests cover the key scenarios. The deferred cleanup gap should be addressed before enabling sandbox_cleanup_delay_seconds with agent-mode automations, but does not block the default configuration.

the sandbox carrying a live conversation is already excluded here.
"""
return bool(run.sandbox_id) and keep_alive is not True
return (

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.

Docker runtime resource leak when sandbox_cleanup_delay_seconds > 0.

Returning True for agent-mode runs without sandbox_id causes the watchdog to take the _defer_sandbox_cleanup branch when a delay is configured. That function returns immediately at if not sandbox_id: return without stamping sandbox_cleanup_due_at, so cleanup_due_sandboxes never picks these runs up. Even if it did, cleanup_due_sandboxes guards on if run.sandbox_id: and would skip the cleanup call.

Result: Docker agent-mode runtimes that complete or time out are never released when sandbox_cleanup_delay_seconds > 0. The default (0) takes the immediate cleanup_after_verification path and works correctly. Consider calling cleanup_after_verification directly for agent-mode runs when the deferred path would be a no-op.

conversation = RemoteConversation.attach(
workspace=workspace,
conversation_id=UUID(conversation_id),
visualizer=None,

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.

Loads entire conversation event history into memory.

conversation.state.events materializes every event in the conversation, and [event.model_dump(mode="json") for event in ...] serializes them all before scanning for the latest finish tool response. The previous implementation used a paginated search endpoint (limit=100, newest-first). For long agent conversations (hundreds of iterations), this could load a large payload into memory on every outcome lookup. If the SDK offers a filtered/paginated events API, prefer it here.

@all-hands-bot

Copy link
Copy Markdown
Contributor

🤖 OpenHands is reviewing this PR.

Head commit: d1089ae7448e4ff68d330d4084d4b750e0083819
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/76c52b0e-8ddc-4995-aa83-b3b4f7917004

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.

Verdict: Worth merging

Taste Rating: Good taste

This PR introduces a clean ConversationAgentServerBackend that extends LocalAgentServerBackend to provision agent conversations through the SDK's RemoteConversation.create / AsyncRemoteWorkspace APIs. The execution-mode selection is data-driven (a column on Automation / AutomationRun), the runtime discovery is delegated to the server's capabilities endpoint, and credential scoping ensures Docker workers never receive the outer server key. The watchdog polling expansion for agent-mode runs and the dispatcher's concurrency bounding are well-reasoned.

Key observations

No material bugs found. The implementation is sound across all reviewed paths:

  1. Credential scoping (conversation.py lines 111-125, 134-148): Docker workers receive only the inner session key from get_runtime_session_key(), never the outer self.api_key. The build_env_vars guard at line 134 raises if credentials haven't been provisioned, preventing a partially-initialized backend from leaking an empty key.

  2. Conversation ID consistency (dispatcher.py lines 528-535): The conversation_id is persisted to the run record after successful dispatch, using the same value the backend derived from the subject key. This is correctly guarded by ctx.runtime_conversation_id is not None.

  3. Callback suppression (dispatcher.py lines 362-366): Agent-mode backends deliberately omit AUTOMATION_CALLBACK_API_KEY and OPENHANDS_API_KEY from env vars, so the callback URL is not injected when local_api_key is set. Completion is detected through watchdog polling of the SDK runtime. This is the intended design.

  4. Watchdog polling (watchdog.py lines 572-578): Agent-mode runs with a bash_command_id are polled on every scan, which is the primary completion path. The timeout_at.isnot(None) guard ensures runs without a timeout are excluded. With max_concurrent_runs=2, the per-scan load is bounded.

  5. Concurrency bounding (dispatcher.py lines 134-175): The capacity check counts RUNNING agent runs, limits batch_size to 1 when capacity exists, and excludes agent runs entirely when capacity is 0 -- while still allowing script-mode runs to proceed. This is correct.

  6. Migration (025_add_execution_mode.py): Cross-database compatible, uses server_default="script" for both columns, and the downgrade drops both. Clean.

  7. Sync SDK wrapping (conversation_outcome.py, conversation_turn.py): Sync RemoteConversation.attach calls are wrapped in asyncio.to_thread with proper finally cleanup (conversation.close(), workspace.reset_client()). Good pattern.

Merge prerequisite (not a code issue)

The SDK dependency is pinned to a git commit from the open software-agent-sdk#5010 PR. The PR description explicitly states this must be replaced with the released SDK before merging. This is acknowledged and tracked -- not a blocker for code review.

[RISK ASSESSMENT]

  • Overall PR: MEDIUM
  • Rationale: The change introduces a new execution mode that alters the dispatch and completion paths for agent-mode runs. The SDK git pin is an unreleased dependency from an open PR. The watchdog now polls agent runs on every scan, and the cancellation path releases Docker runtimes. All of these are well-tested with focused unit tests covering local/Docker routing, credential scoping, concurrency bounding, and cleanup failure resilience. The risk is medium primarily due to the unreleased SDK dependency and the breadth of the change (32 files, +1377/-538).

KEY INSIGHT: The ConversationAgentServerBackend cleanly separates runtime provisioning (server capabilities query + SDK conversation creation) from execution (shared upload/bash helpers), and the credential boundary between outer server key and inner session key is maintained consistently across creation, dispatch, verification, and cleanup.


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 (e.g., "Security concerns about X do not apply here because Y"). See the customization docs for the required frontmatter format.
  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 15, 2026 02:19

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

🤖 OpenHands is reviewing this PR.

Head commit: 9f538d5e5756fdbaacabc507f4140b61fda4ccbe
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/30852d22-ec19-4305-b776-33de0f25ff25

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 adds an explicit script/agent execution mode to automation definitions and run snapshots, introduces a ConversationAgentServerBackend that provisions SDK conversations for agent-mode runs, bounds concurrent agent conversations via dispatcher capacity logic, and migrates the upload/bash/verify/outcome/turn helpers from raw HTTP to the SDK's RemoteWorkspace/RemoteConversation APIs.

🟢 Good taste — The execution-mode field is a clean, explicit discriminator that replaces implicit backend selection. The capacity gating in the dispatcher correctly keeps host-side scripts dispatchable while bounding agent conversations. The watchdog's per-scan polling of agent runs (without sharing the callback credential) is a sensible primary completion path. The SDK migration eliminates hand-rolled HTTP plumbing in favor of typed SDK methods.

Findings

No material bugs, security problems, or design flaws found.

Noted (non-blocking): The pyproject.toml pins openhands-sdk to a git commit on the software-agent-sdk feature branch (e9392e7c...). The PR description explicitly documents this as a temporary integration pin that must be replaced with the SDK release before merge. This is a first-party package (same org), pinned to an immutable commit, so it is reproducible. Confirm the SDK release is published before merging.

[RISK ASSESSMENT]

  • Overall PR: 🟡 MEDIUM — The change touches core dispatch, execution, verification, and completion paths. The temporary SDK git pin is a merge prerequisite that must be resolved. The core logic is sound and well-tested.

VERDICT:Worth merging — Core logic is sound, test coverage is comprehensive across both execution modes and runtime kinds.

KEY INSIGHT: Making execution mode an explicit, snapshotted field on both the automation and its runs cleanly separates deterministic script execution from agent conversation provisioning without coupling backend selection to the presence of a profile.


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 (e.g., "Security concerns about X do not apply here because Y"). See the customization docs for the required frontmatter format.
  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 👍 or 👎 to give feedback.

@all-hands-bot
all-hands-bot dismissed their stale review September 15, 2026 02:22

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

Code review of PR #449 (feat: dispatch automation bundles through SDK conversation runtimes) is complete. I posted an APPROVED review to GitHub.

Review findings: No material bugs, security problems, or design flaws found. The PR cleanly introduces an explicit script/agent execution mode with proper migration, dispatcher capacity gating, and SDK API migration. The only noted item is the temporary SDK git commit pin in pyproject.toml, which is explicitly documented as a merge prerequisite.

Risk assessment: 🟡 MEDIUM — Core logic is sound and well-tested, but the temporary SDK git pin must be replaced with a release before merge.

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

Co-authored-by: openhands <openhands@all-hands.dev>

@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 an explicit execution_scope (run vs conversation) that separates execution mode from agent-server provisioning. The design cleanly splits the old monolithic ExecutionBackend into AgentServerProvider (cloud sandbox vs existing server) and ExecutionBackend (run-scoped vs conversation-scoped), composed at dispatch time. The conversation backend provisions a profile-backed conversation, injects scoped runtime credentials (never the shared callback key), and relies on watchdog polling for completion. Run-scoped execution retains its existing callback path.

Assessment

Taste: Good taste. The provider/backend separation eliminates the old is_local_mode branching throughout the codebase. The execution_scope snapshot on AutomationRun ensures later definition edits do not affect in-flight runs. The migration is backward-compatible with server_default="run".

Key observations (non-blocking)

  1. SDK git pin (pyproject.toml): openhands-sdk is pinned to a git commit instead of a released version. This is explicitly acknowledged as a merge prerequisite in the PR description and README. The pin must be replaced with the SDK release before merging.

  2. Local-mode batch throttling (dispatcher.py): When conversation capacity > 0 in local mode, batch_size = min(batch_size, 1, conversation_capacity) throttles ALL pending runs (including run-scoped) to 1 per poll. Reasonable for single-tenant local mode but could slow run-scoped dispatch. Not a bug.

  3. Watchdog polling (watchdog.py): Conversation-scoped runs are verified on every scan (default 60s) regardless of timeout. This is the intended primary completion path. The SQL condition is correct.

  4. Callback URL conditioning (dispatcher.py): AUTOMATION_CALLBACK_URL is set only when the worker has callback credentials or no local_api_key is configured. ConversationBackend workers deliberately do not receive callback credentials, falling back to watchdog polling. Sound.

  5. Runtime credential isolation (conversation.py): Docker workers receive only the inner session key, never the host server key. Tests explicitly assert this. Good security boundary.

No blocking issues found

  • No KeyError risk at env_vars["AUTOMATION_CONVERSATION_ID"]: when ctx.runtime_conversation_id is non-None (conversation backend), build_env_vars() always sets that key.
  • Cancel path for conversation runs is best-effort with proper exception handling.
  • Migration uses generic SQLAlchemy types (cross-database compatible).
  • Tests exercise real code paths with mock HTTP transports, not just mock-call assertions.

Risk Assessment

MEDIUM - Large architectural change (38 files, +1663/-706) introducing a new execution path. The SDK git pin is a merge prerequisite. No bugs or security issues found. The design is clean and well-tested.

Verdict: Worth merging (after replacing the SDK git pin with a release).

Key insight: The provider/backend separation is the right abstraction - it makes execution scope and server provisioning orthogonal concerns, eliminating mode-specific branching throughout the dispatcher and watchdog.

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.

Give local and Docker automation runs the same conversation execution contract Run local automation bundles in isolated Docker conversation runtimes

3 participants