Skip to content

Add OpenHands agent handoff workflow#22

Merged
rickylabs merged 3 commits into
mainfrom
chore/openhands-agent-handoff
Jun 9, 2026
Merged

Add OpenHands agent handoff workflow#22
rickylabs merged 3 commits into
mainfrom
chore/openhands-agent-handoff

Conversation

@rickylabs

@rickylabs rickylabs commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Summary

This PR adds a committed OpenHands foundation for NetScript so local agents, GitHub Actions, and a long-running VPS/Web UI deployment can use the same handoff protocol.

The branch is based directly on origin/main, not on feat/package-quality, so it is intended to merge cleanly into main after review.

Why

The recent PR #18 workflow showed the real operating pattern we want to preserve:

  • trigger a cloud agent from the PR thread with a model-qualified instruction,
  • include use harness when the task is an evaluator or harness phase,
  • handle legitimate Augment/Copilot review comments before writing the final verdict,
  • publish a PR comment that summarizes fixes, validations, and remaining risks.

This PR turns that pattern into repository-owned automation and documentation instead of relying on local IDE/agent behavior.

Implementation

1. GitHub Actions OpenHands runner

Adds .github/workflows/openhands-agent.yml.

Supported triggers:

  • manual workflow_dispatch with prompt, model, target PR, and output mode,
  • issue or PR labels: fix-me, openhands, or agent:<profile-or-model>,
  • trusted PR/issue comments containing @openhands-agent,
  • commit messages containing [openhands ...].

The comment trigger is gated to OWNER, MEMBER, or COLLABORATOR author associations because the job has write permissions.

Per-run model selection precedence:

  1. manual workflow model input,
  2. model=... in comment or commit message,
  3. agent=<profile> in comment or commit message,
  4. agent:<profile-or-literal> label,
  5. repository variable OPENHANDS_DEFAULT_MODEL,
  6. fallback anthropic/claude-sonnet-4.

Profiles currently map to:

  • sonnet -> anthropic/claude-sonnet-4,
  • gpt -> openai/gpt-5.1,
  • gemini -> gemini/gemini-2.5-pro.

Output modes:

  • pr-comment: one summary comment,
  • respond-comments: one summary comment that explicitly addresses review/issue comments,
  • thread-replies: summary plus optional PR review-thread replies from .llm/tmp/openhands/replies.json,
  • summary-only: upload artifacts without commenting.

2. OpenHands SDK runner and repo bootstrap

Adds .openhands/agent_runner.py, .openhands/setup.sh, and .openhands/microagents/repo.md.

The workflow uses the current OpenHands SDK pattern for custom task execution instead of only a fixed resolver action. That gives us explicit control over prompt composition, required summary artifacts, fetched PR comment context, and optional threaded replies.

The setup script mirrors the existing Copilot setup assumptions:

  • Deno 2,
  • .NET 10,
  • Aspire CLI,
  • Docker verification,
  • Deno dependency cache warming,
  • NuGet/workload visibility.

3. Handoff protocol documentation

Adds AGENTS-handoff.md.

This is the human/agent-facing protocol for:

  • triggering cloud runs from labels, comments, commit messages, and Actions dispatch,
  • choosing a model per run,
  • choosing output behavior,
  • preserving the PAT-vs-GITHUB_TOKEN chain-trigger rule,
  • handling Augment/Copilot review comments,
  • deciding when to use Actions versus the VPS/Web UI session.

4. Local-agent skill and harness integration

Adds .agents/skills/openhands-handoff/SKILL.md and .llm/harness/workflow/agent-handoff.md.

Updates:

  • .agents/skills/netscript-harness/SKILL.md now points harness-aware agents to the OpenHands handoff workflow when a run crosses local/cloud/VPS boundaries.
  • .llm/harness/README.md now states that GitHub comments are supporting evidence, not replacements for harness artifacts.

The important harness invariant is preserved: PLAN-EVAL and IMPL-EVAL still require separate sessions. An OpenHands cloud run may perform one role, but it must not self-certify work it generated in the same session.

5. VPS / Dokploy scaffold

Adds ops/openhands/docker-compose.yml and ops/openhands/.env.example.

This gives the long-running OpenHands Web UI path a committed starting point for Dokploy or direct Docker Compose deployment. The intended split is:

  • Actions workflow for short PR/issue tasks, evaluator passes, review-comment fixes, and mobile triggers,
  • VPS Web UI/SDK session for longer multi-step work that needs pause/resume or human-in-the-loop steering.

Required GitHub configuration

Repository secrets:

  • LLM_API_KEY_<PROVIDER> preferred, selected automatically from the resolved provider.
  • LLM_API_KEY optional fallback when a provider-specific key is absent.
  • LLM_BASE_URL_<PROVIDER> optional for provider-specific OpenAI-compatible gateways.
  • LLM_BASE_URL optional fallback base URL.
  • PAT_TOKEN strongly recommended when cloud-created comments, commits, or labels should trigger follow-up workflows.

Common provider-specific secrets:

Selected model/provider Secret used first Optional base URL key
anthropic/... LLM_API_KEY_ANTHROPIC LLM_BASE_URL_ANTHROPIC
openai/... LLM_API_KEY_OPENAI LLM_BASE_URL_OPENAI
gemini/... or google/... LLM_API_KEY_GEMINI LLM_BASE_URL_GEMINI
openrouter/... or provider=openrouter LLM_API_KEY_OPENROUTER LLM_BASE_URL_OPENROUTER

Repository variable:

  • OPENHANDS_DEFAULT_MODEL optional, defaults to anthropic/claude-sonnet-4.

The workflow infers provider from the selected model prefix unless the trigger includes provider=....
Example: @openhands-agent provider=openrouter model=openai/gpt-5.1 ... uses LLM_API_KEY_OPENROUTER while keeping the selected model explicit.

Validation

Ran on this branch:

  • deno fmt --check .github/workflows/openhands-agent.yml AGENTS-handoff.md .agents/skills/openhands-handoff/SKILL.md .llm/harness/workflow/agent-handoff.md .openhands/microagents/repo.md ops/openhands/docker-compose.yml ops/openhands/.env.example .agents/skills/netscript-harness/SKILL.md .llm/harness/README.md PASS.
  • python -B -m py_compile .openhands/agent_runner.py PASS.
  • deno eval --no-lock "import { parse } from 'jsr:@std/yaml'; for (const path of ['.github/workflows/openhands-agent.yml','ops/openhands/docker-compose.yml']) parse(await Deno.readTextFile(path));" PASS.

Deno emitted pre-existing workspace warnings from examples/playground/deno.json about nodeModulesDir, unstable, and workspace fields being root-only. They are unrelated to this PR.

Notes for reviewers

This PR intentionally does not depend on the feat/package-quality branch. During PR prep, the work was moved onto a clean branch from origin/main to keep the merge path direct.

The workflow uses a trusted-comment gate because the job has write permissions. Labels and manual dispatch are already permission-gated by GitHub.

@rickylabs

Copy link
Copy Markdown
Owner Author

Sub-implementation 1 — GitHub Actions cloud agent

Added .github/workflows/openhands-agent.yml as the short-run cloud entry point.

What it covers:

  • PR/issue label triggers: fix-me, openhands, agent:<profile-or-model>.
  • Trusted PR/issue comment triggers with @openhands-agent.
  • Commit-message triggers with [openhands ...].
  • Manual dispatch with prompt/model/output controls.
  • Per-run model resolution from workflow input, comment/commit model=..., agent=..., labels, repo var, then default.
  • Output modes: pr-comment, respond-comments, thread-replies, and summary-only.
  • PR context capture into .llm/tmp/openhands/issue-comments.json and .llm/tmp/openhands/pr-review-comments.json.
  • Summary posting from .llm/tmp/openhands/summary.md.
  • Optional review-thread replies from .llm/tmp/openhands/replies.json.

Security note: comment triggers are limited to owner/member/collaborator author associations because the workflow has write permissions.

@rickylabs

Copy link
Copy Markdown
Owner Author

Sub-implementation 2 — OpenHands SDK runner and repo bootstrap

Added the .openhands/ runtime files:

  • .openhands/agent_runner.py runs an OpenHands SDK conversation from the prepared prompt file.
  • .openhands/setup.sh translates the existing Copilot bootstrap assumptions into OpenHands setup: Deno 2, .NET 10, Aspire CLI, Docker checks, Deno cache warmup, and NuGet/workload visibility.
  • .openhands/microagents/repo.md gives OpenHands a compact repo contract: read AGENTS.md, honor harness/doctrine skills, use rtk when available, avoid destructive git operations, and always write .llm/tmp/openhands/summary.md.

This is why the Actions workflow can enforce output behavior instead of depending only on a generic resolver flow.

@rickylabs

Copy link
Copy Markdown
Owner Author

Sub-implementation 3 — Agent handoff protocol

Added AGENTS-handoff.md as the shared protocol for local agents, OpenHands Actions, OpenHands VPS sessions, Copilot, and Augment.

It documents:

  • trigger syntax from mobile, local agents, or cloud agents,
  • model selection precedence,
  • output-mode semantics,
  • the PAT/GitHub App token rule for chainable cloud-created events,
  • how Augment/Copilot review comments should be fetched and answered,
  • when to use Actions versus a long-running VPS/Web UI session.

This is the repo-level message-bus contract: GitHub comments carry handoffs, while artifacts carry durable execution state.

@rickylabs

Copy link
Copy Markdown
Owner Author

Sub-implementation 4 — Skill and harness integration

Added .agents/skills/openhands-handoff/SKILL.md so local agents can discover and follow the OpenHands protocol without re-reading the full workflow.

Added .llm/harness/workflow/agent-handoff.md and linked it from:

  • .agents/skills/netscript-harness/SKILL.md,
  • .llm/harness/README.md.

The harness rule is explicit: GitHub comments are evidence and coordination, not a replacement for .llm/tmp/run/<run-id>/ artifacts. PLAN-EVAL and IMPL-EVAL remain separate-session responsibilities.

@rickylabs

Copy link
Copy Markdown
Owner Author

Sub-implementation 5 — VPS / Dokploy deployment scaffold

Added ops/openhands/docker-compose.yml plus ops/openhands/.env.example.

This is the committed starting point for the long-running OpenHands Web UI path on the VPS/Dokploy side.

Intended split:

  • GitHub Actions OpenHands: short PR/issue work, evaluator passes, review-comment fixes, mobile-triggered jobs.
  • VPS OpenHands Web UI/SDK: multi-step sessions, human-in-the-loop checkpoints, and work that should not fit inside a single Actions run.

The .env.example is force-added because the repo ignores .env.* by default.

@augmentcode

augmentcode Bot commented Jun 9, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: This PR establishes a repo-owned OpenHands “handoff” foundation so local agents, GitHub Actions runs, and a VPS/Web UI deployment can share the same trigger and reporting protocol.

Changes:

  • Added a new GitHub Actions workflow (.github/workflows/openhands-agent.yml) to trigger OpenHands runs via dispatch, labels, trusted comments, and commit-message markers.
  • Introduced an OpenHands SDK runner (.openhands/agent_runner.py) and bootstrap script (.openhands/setup.sh) to standardize toolchain setup and run execution.
  • Documented the cross-agent trigger and output-mode protocol in AGENTS-handoff.md.
  • Integrated handoff guidance into the harness workflow via .llm/harness/workflow/agent-handoff.md and updates to existing harness docs/skills.
  • Added a skill routing card for handoffs (.agents/skills/openhands-handoff/SKILL.md) and OpenHands repo context (.openhands/microagents/repo.md).
  • Added an initial VPS/Dokploy deployment scaffold (ops/openhands/docker-compose.yml + env example) for long-running sessions.

Technical Notes: The workflow posts a required summary artifact (.llm/tmp/openhands/summary.md) back to the PR/issue (unless summary-only) and supports optional review-thread replies via .llm/tmp/openhands/replies.json.

🤖 Was this summary useful? React with 👍 or 👎

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

Review completed. 5 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.


- name: Run OpenHands
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}

@augmentcode augmentcode Bot Jun 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In .github/workflows/openhands-agent.yml:L253-L257, this runs repo code from checkout_ref while injecting LLM_API_KEY/PAT_TOKEN-backed credentials; a mistakenly-applied label/comment on a less-trusted PR branch could execute attacker-controlled code with secrets. Consider adding explicit trust/fork guards (e.g., only allow same-repo PR heads) before running steps that have secret access.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

for (const label of payload.pull_request?.labels ?? []) labels.add(label.name);
if (payload.label?.name) labels.add(payload.label.name);

const rawText = [

@augmentcode augmentcode Bot Jun 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In .github/workflows/openhands-agent.yml:L100-L107, rawText includes payload.issue?.body/payload.pull_request?.body, so a model=... or output=... embedded in the issue/PR description can override label/profile selection. That isn’t reflected in the documented precedence in AGENTS-handoff.md (comment/commit/label), so it’s worth confirming this is intended.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.


- name: Install OpenHands SDK
run: |
uv pip install --system "openhands-sdk @ git+https://github.com/OpenHands/software-agent-sdk.git@main#subdirectory=openhands-sdk"

@augmentcode augmentcode Bot Jun 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In .github/workflows/openhands-agent.yml:L248-L249, installing openhands-sdk/openhands-tools from @main makes runs non-reproducible and can break unexpectedly if upstream changes. Consider pinning to a tag or specific commit SHA to stabilize CI behavior.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

git config user.name "OpenHands Bot"
git add -A
git commit -m "chore(openhands): apply agent changes"
git push "https://x-access-token:${TOKEN}@github.com/${CHECKOUT_REPO}.git" "HEAD:${CHECKOUT_REF}"

@augmentcode augmentcode Bot Jun 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In .github/workflows/openhands-agent.yml:L274, git push ... HEAD:${CHECKOUT_REF} will fail for forked PRs or when the selected token can’t push to checkout_repo, which can turn a successful agent run into a failed workflow. Consider detecting fork/permission scenarios and skipping the push (or falling back to the non-PR PR-creation path) to avoid noisy failures.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread .openhands/setup.sh
curl -fsSL https://aspire.dev/install.sh | bash
fi

echo "$DENO_INSTALL/bin" >> "$GITHUB_PATH" 2>/dev/null || true

@augmentcode augmentcode Bot Jun 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In .openhands/setup.sh:L22-L24, set -u will error if GITHUB_PATH isn’t defined (e.g., running the script outside GitHub Actions), even though the command redirects errors to /dev/null. Consider guarding these writes so local/VPS bootstraps don’t fail due to an unbound GITHUB_PATH.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@rickylabs

Copy link
Copy Markdown
Owner Author

Sub-implementation 6 — Provider-specific LLM secrets

Updated the workflow so the selected model/provider chooses the matching API key automatically.

New behavior:

  • anthropic/... -> LLM_API_KEY_ANTHROPIC
  • openai/... -> LLM_API_KEY_OPENAI
  • gemini/... or google/... -> LLM_API_KEY_GEMINI
  • openrouter/... or provider=openrouter -> LLM_API_KEY_OPENROUTER

The generic LLM_API_KEY remains a fallback, so existing single-key setup still works. Base URLs follow the same pattern with LLM_BASE_URL_<PROVIDER> and fallback to LLM_BASE_URL.

This makes per-run model selection practical without editing secrets between Anthropic/OpenAI/Gemini/OpenRouter runs.

@rickylabs

Copy link
Copy Markdown
Owner Author

Sub-implementation 7 — Runtime hydration for older target branches

Added a compatibility step that downloads .openhands/setup.sh and .openhands/agent_runner.py from the workflow commit into /tmp/openhands-runtime after checking out the target branch.

Reason: issue-comment runs on existing PRs, such as #19, check out the PR head branch. Those branches predate this automation and do not contain .openhands/. The workflow now carries its runtime with it while still operating on the target branch contents.

@rickylabs
rickylabs merged commit 027d4f8 into main Jun 9, 2026
1 check passed
@rickylabs
rickylabs deleted the chore/openhands-agent-handoff branch June 22, 2026 21:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant