Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions .agents/skills/openhands-handoff/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,18 @@ required summary artifacts keep local and cloud agents synchronized.

## Key Concepts

| Concept | Meaning |
| ---------------- | --------------------------------------------------------------------------------------------- |
| Actions agent | `.github/workflows/openhands-agent.yml`, used for short cloud runs. |
| VPS session | Long-running OpenHands Web UI/SDK deployment from `ops/openhands/docker-compose.yml`. |
| Model profile | `sonnet`, `gpt`, or `gemini`; maps to a LiteLLM model id in the workflow. |
| Literal model | Any LiteLLM-compatible `provider/model` string supplied with `model=...`. |
| Provider secret | `LLM_API_KEY_<PROVIDER>`, inferred from the model prefix, with `LLM_API_KEY` fallback. |
| Output mode | `pr-comment`, `respond-comments`, `thread-replies`, or `summary-only`. |
| Summary artifact | `.llm/tmp/openhands/summary.md`, required before the workflow exits. |
| Thread replies | Optional `.llm/tmp/openhands/replies.json` review-comment replies. |
| Chainable token | `PAT_TOKEN` or GitHub App token; required for cloud-created events to trigger more workflows. |
| Concept | Meaning |
| ---------------- | ----------------------------------------------------------------------------------------------- |
| Actions agent | `.github/workflows/openhands-agent.yml`, used for short cloud runs. |
| VPS session | Long-running OpenHands Web UI/SDK deployment from `ops/openhands/docker-compose.yml`. |
| Model profile | `sonnet`, `gpt`, or `gemini`; maps to a LiteLLM model id in the workflow. |
| Literal model | Any LiteLLM-compatible `provider/model` string supplied with `model=...`. |
| Provider secret | `LLM_API_KEY_<PROVIDER>`, inferred from the model prefix, with `LLM_API_KEY` fallback. |
| Output mode | `pr-comment`, `respond-comments`, `thread-replies`, or `summary-only`. |
| Summary artifact | `.llm/tmp/openhands/summary.md`, required before the workflow exits. |
| Status comment | One workflow-owned PR/issue comment that starts as running and is edited with the final result. |
| Thread replies | Optional `.llm/tmp/openhands/replies.json` review-comment replies. |
| Chainable token | `PAT_TOKEN` or GitHub App token; required for cloud-created events to trigger more workflows. |

## Workflow

Expand Down Expand Up @@ -66,6 +67,8 @@ required summary artifacts keep local and cloud agents synchronized.
`replies.json`. Use `respond-comments` for a single high-quality response when IDs are uncertain.
- **Skipping the summary artifact**: GitHub comments and PR bodies come from
`.llm/tmp/openhands/summary.md`; missing summaries produce weak handoffs.
- **Posting duplicate comments**: the workflow owns the status/final comment. Agents write
artifacts; they do not call `gh issue comment` directly.
- **Using Actions for long sessions**: move multi-step, human-in-the-loop work to the VPS Web UI.

## Reference Files
Expand Down
79 changes: 77 additions & 2 deletions .github/workflows/openhands-agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,61 @@ jobs:
core.setOutput('prompt', prompt);
core.setOutput('issue_number', String(issueNumber));
core.setOutput('is_pr', String(isPr));
core.setOutput('trigger_comment_id', String(payload.comment?.id || ''));
core.setOutput('checkout_repo', checkoutRepo);
core.setOutput('checkout_ref', checkoutRef);
core.setOutput('base_branch', baseBranch);
core.setOutput('branch_name', `openhands/${issueNumber || context.runId}-${context.runAttempt}`);

- name: Acknowledge trigger and post run link
id: status-comment
if: steps.request.outputs.output_mode != 'summary-only' && steps.request.outputs.issue_number != ''
uses: actions/github-script@v7
env:
ISSUE_NUMBER: ${{ steps.request.outputs.issue_number }}
TRIGGER_COMMENT_ID: ${{ steps.request.outputs.trigger_comment_id }}
MODEL: ${{ steps.request.outputs.model }}
PROVIDER: ${{ steps.request.outputs.provider }}
OUTPUT_MODE: ${{ steps.request.outputs.output_mode }}
RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
with:
github-token: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }}
script: |
const issue_number = Number(process.env.ISSUE_NUMBER);
const triggerCommentId = process.env.TRIGGER_COMMENT_ID;

if (triggerCommentId) {
try {
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: Number(triggerCommentId),
content: 'eyes',
});
} catch (error) {
core.notice(`Could not add trigger reaction: ${error.message}`);
}
}

const body = [
'<!-- openhands-agent-summary -->',
'## OpenHands Agent — Running',
'',
`Model: \`${process.env.MODEL}\``,
`Provider: \`${process.env.PROVIDER}\``,
`Output mode: \`${process.env.OUTPUT_MODE}\``,
'',
`Run: ${process.env.RUN_URL}`,
].join('\n');

const { data: comment } = await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
body,
});
core.setOutput('comment_id', String(comment.id));

- name: Checkout repository
uses: actions/checkout@v6
with:
Expand Down Expand Up @@ -246,6 +296,7 @@ jobs:
- Use rtk for read-heavy git/grep/gh/docker commands when it is available.
- Preserve user changes and avoid destructive git commands.
- Run the smallest validation that proves the change.
- Do not post GitHub issue or PR comments directly. The workflow owns GitHub comments.
- Write .llm/tmp/openhands/summary.md before exit. Include Summary, Changes, Validation,
Responses to review comments or issue comments when relevant, and Remaining risks.
- If output_mode is thread-replies, optionally write .llm/tmp/openhands/replies.json as
Expand Down Expand Up @@ -376,26 +427,50 @@ jobs:
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ steps.request.outputs.issue_number }}
STATUS_COMMENT_ID: ${{ steps.status-comment.outputs.comment_id }}
MODEL: ${{ steps.request.outputs.model }}
PROVIDER: ${{ steps.request.outputs.provider }}
JOB_STATUS: ${{ job.status }}
RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
if [ ! -f .llm/tmp/openhands/summary.md ]; then
cat > .llm/tmp/openhands/summary.md <<SUMMARY
## OpenHands task did not write a summary
# OpenHands Agent Summary

## Status
The workflow reached final comment publication, but the agent did not write `.llm/tmp/openhands/summary.md`.

Model: \`${MODEL}\`
Run: ${RUN_URL}
SUMMARY
fi
{
echo "<!-- openhands-agent-summary -->"
echo "## OpenHands Agent — ${JOB_STATUS}"
echo
echo "Model: \`${MODEL}\`"
echo "Provider: \`${PROVIDER}\`"
echo
cat .llm/tmp/openhands/summary.md
echo
echo "Run: ${RUN_URL}"
} > .llm/tmp/openhands/comment.md
gh issue comment "${ISSUE_NUMBER}" --body-file .llm/tmp/openhands/comment.md
if [ -n "${STATUS_COMMENT_ID}" ]; then
python - <<'PY'
import json

with open(".llm/tmp/openhands/comment.md", encoding="utf-8") as source:
payload = {"body": source.read()}
with open(".llm/tmp/openhands/comment.json", "w", encoding="utf-8") as target:
json.dump(payload, target)
PY
gh api --method PATCH \
"repos/${GITHUB_REPOSITORY}/issues/comments/${STATUS_COMMENT_ID}" \
--input .llm/tmp/openhands/comment.json \
--jq .html_url >/dev/null
else
gh issue comment "${ISSUE_NUMBER}" --body-file .llm/tmp/openhands/comment.md
fi

- name: Post review-thread replies
if: always() && steps.request.outputs.output_mode == 'thread-replies' && steps.request.outputs.is_pr == 'true'
Expand Down
3 changes: 3 additions & 0 deletions .openhands/microagents/repo.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ Before finishing an OpenHands workflow run, write `.llm/tmp/openhands/summary.md
- responses to relevant PR, issue, or review comments,
- remaining risks or follow-up work.

Do not post GitHub issue or PR comments directly. The workflow reacts to the trigger, creates the
running status comment, and edits that comment with this summary artifact.

When the workflow output mode is `thread-replies`, optionally write
`.llm/tmp/openhands/replies.json`:

Expand Down
4 changes: 4 additions & 0 deletions AGENTS-handoff.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ provider-specific base URLs use the same suffix pattern, such as `LLM_BASE_URL_O

The agent must write `.llm/tmp/openhands/summary.md` before exit.

The workflow owns GitHub comments: it reacts to the trigger comment, posts one running status
comment with the Actions URL, then edits that same comment with the final summary. Agents should not
post their own PR or issue comments during OpenHands runs.

## Token Rule

GitHub does not trigger follow-up workflows from events created with the default `GITHUB_TOKEN`. Use
Expand Down