Skip to content

fix: attach task outcome to finish tool - #4465

Open
malhotra5 wants to merge 11 commits into
mainfrom
task-outcome-reporting
Open

fix: attach task outcome to finish tool#4465
malhotra5 wants to merge 11 commits into
mainfrom
task-outcome-reporting

Conversation

@malhotra5

@malhotra5 malhotra5 commented Aug 11, 2026

Copy link
Copy Markdown
Member

HUMAN:
Tested that this works via the example script as part of this PR.


AGENT:

Why

Automation runs need a first-class, automatically integrated way for agents to report outcome metadata such as success, blockers, and whether user action is needed. This should not require each automation author to configure a custom contract, and it should avoid a separate LLM call or standalone reporting tool when the agent already has a terminal finish action.

Summary

  • Adds structured TaskOutcome reporting to the default FinishTool, storing the latest outcome on conversation state for SDK, remote conversation, and agent-server consumers.
  • Preserves system-authored fallback task outcomes for runtime, harness, and LLM failures when the agent never reaches finish.
  • Removes the standalone report_task_outcome tool, limits TaskOutcome.source to agent or system, and updates examples/tests for the new flow.

Issue Number

Links OpenHands/automation#323.

How to Test

Reviewer steps:

  1. Run targeted task outcome tests:

    uv run pytest tests/sdk/tool/test_finish_task_outcome.py tests/sdk/tool/test_response_schema.py -q
  2. Run lint/type checks for touched test files:

    uv run ruff check tests/sdk/tool/test_finish_task_outcome.py tests/cross/test_event_loss_repro.py tests/cross/test_remote_conversation_live_server.py examples/01_standalone_sdk/57_task_outcome_missing_mcp.py
    uv run pyright tests/sdk/tool/test_finish_task_outcome.py tests/cross/test_event_loss_repro.py tests/cross/test_remote_conversation_live_server.py
  3. Run the standalone example after configuring an LLM environment:

    uv run python examples/01_standalone_sdk/57_task_outcome_missing_mcp.py

Evidence from this PR:

  • Local targeted SDK test run: 54 passed, 5 warnings for tests/sdk/tool/test_finish_task_outcome.py and tests/sdk/tool/test_response_schema.py.
  • Local duplicate example check passed: python .github/scripts/check_duplicate_example_numbers.py.
  • Local lint/type checks passed for touched files.
  • End-to-end/manual example output is included in .pr/example_result.txt and .pr/task_outcome_missing_mcp_result.md.
  • GitHub CI is green on the latest head: 35 successful, 0 failing, 0 pending, 2 skipped.

Video/Screenshots

Design Doc

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

  • Companion docs branch pushed: OpenHands/docs branch task-outcome-reporting, documenting examples/01_standalone_sdk/57_task_outcome_missing_mcp.py so the documented examples check passes.
  • TaskOutcome.source is intentionally limited to agent and system.
  • This PR description was updated by an AI agent (OpenHands) on behalf of the user.

🐳 Agent Server images for this PR — GHCR package, pull/run commands, and all pushed tags (click to expand)

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:be17960-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-be17960-python \
  ghcr.io/openhands/agent-server:be17960-python

All tags pushed for this build

ghcr.io/openhands/agent-server:be17960-golang-amd64
ghcr.io/openhands/agent-server:be17960b7846fdfc4bd66ce2fb6dd40bd890f6b3-golang-amd64
ghcr.io/openhands/agent-server:task-outcome-reporting-golang-amd64
ghcr.io/openhands/agent-server:be17960-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:be17960-golang-arm64
ghcr.io/openhands/agent-server:be17960b7846fdfc4bd66ce2fb6dd40bd890f6b3-golang-arm64
ghcr.io/openhands/agent-server:task-outcome-reporting-golang-arm64
ghcr.io/openhands/agent-server:be17960-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:be17960-java-amd64
ghcr.io/openhands/agent-server:be17960b7846fdfc4bd66ce2fb6dd40bd890f6b3-java-amd64
ghcr.io/openhands/agent-server:task-outcome-reporting-java-amd64
ghcr.io/openhands/agent-server:be17960-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:be17960-java-arm64
ghcr.io/openhands/agent-server:be17960b7846fdfc4bd66ce2fb6dd40bd890f6b3-java-arm64
ghcr.io/openhands/agent-server:task-outcome-reporting-java-arm64
ghcr.io/openhands/agent-server:be17960-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:be17960-python-amd64
ghcr.io/openhands/agent-server:be17960b7846fdfc4bd66ce2fb6dd40bd890f6b3-python-amd64
ghcr.io/openhands/agent-server:task-outcome-reporting-python-amd64
ghcr.io/openhands/agent-server:be17960-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:be17960-python-arm64
ghcr.io/openhands/agent-server:be17960b7846fdfc4bd66ce2fb6dd40bd890f6b3-python-arm64
ghcr.io/openhands/agent-server:task-outcome-reporting-python-arm64
ghcr.io/openhands/agent-server:be17960-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:be17960-golang
ghcr.io/openhands/agent-server:be17960b7846fdfc4bd66ce2fb6dd40bd890f6b3-golang
ghcr.io/openhands/agent-server:task-outcome-reporting-golang
ghcr.io/openhands/agent-server:be17960-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:be17960-java
ghcr.io/openhands/agent-server:be17960b7846fdfc4bd66ce2fb6dd40bd890f6b3-java
ghcr.io/openhands/agent-server:task-outcome-reporting-java
ghcr.io/openhands/agent-server:be17960-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:be17960-python
ghcr.io/openhands/agent-server:be17960b7846fdfc4bd66ce2fb6dd40bd890f6b3-python
ghcr.io/openhands/agent-server:task-outcome-reporting-python
ghcr.io/openhands/agent-server:be17960-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., be17960-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., be17960-python-amd64) are also available if needed

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

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@malhotra5
malhotra5 marked this pull request as draft August 11, 2026 20:55
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk
   __init__.py28293%117–118
openhands-sdk/openhands/sdk/agent
   base.py3784289%95–96, 252, 319, 523, 609, 619, 627–628, 670, 672–673, 755, 792–793, 803–804, 829–835, 844, 848, 851, 926, 929, 941–943, 948, 950, 953, 960, 964, 973, 987, 990, 1006, 1052
openhands-sdk/openhands/sdk/conversation
   base.py114397%234, 242, 296
   state.py277698%379–380, 389–391, 567
openhands-sdk/openhands/sdk/conversation/impl
   local_conversation.py10999092%164, 619–620, 653, 695, 1023, 1047–1048, 1053, 1068, 1070, 1174, 1190, 1240, 1266, 1348, 1352–1358, 1423, 1443–1445, 1494, 1513–1515, 1853, 1858–1860, 1879–1880, 1895, 2113, 2116–2117, 2142, 2173, 2179, 2260, 2267, 2270, 2273, 2277–2278, 2282–2283, 2286, 2293, 2318, 2322, 2325, 2344, 2396, 2399, 2438, 2445–2446, 2454, 2458–2460, 2467, 2504–2510, 2513, 2516, 2523, 2593, 2598, 2726–2727, 2745–2746, 2779, 2983, 2987, 3057, 3064–3065
   remote_conversation.py7729388%92, 167, 194, 207, 209–212, 238, 256–257, 268–271, 278–279, 284–287, 371, 381–383, 389, 467, 614–617, 619, 645–649, 654–657, 660, 676, 681–685, 823, 881–882, 886–887, 901, 915–918, 959, 972–973, 997–998, 1038–1039, 1075, 1085, 1089, 1098–1099, 1138, 1288–1289, 1383–1384, 1388, 1393–1397, 1403–1409, 1422, 1427, 1477, 1753–1754
openhands-sdk/openhands/sdk/tool/builtins
   finish.py29197%90
openhands-sdk/openhands/sdk/workspace
   base.py80495%21, 100, 274, 285
openhands-workspace/openhands/workspace/cloud
   workspace.py38910473%216–218, 289, 308, 311–313, 316, 322, 324–326, 348, 351, 356, 358–359, 361–363, 365, 367–368, 371–374, 376–377, 379–380, 382, 384–386, 389–390, 394–405, 410, 479–480, 495–496, 501, 530–532, 769, 822–823, 844, 869–870, 873–875, 878, 884–885, 889–894, 896–900, 938, 961, 971–973, 976, 982–984, 989–990, 992–1002
TOTAL40460716282% 

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

@VascoSch92 VascoSch92 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we do have structured output for that

Co-authored-by: openhands <openhands@all-hands.dev>
@malhotra5 malhotra5 changed the title feat: add task outcome reporting tool fix: attach task outcome to finish tool Aug 11, 2026
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

Copy link
Copy Markdown
Contributor

📁 PR Artifacts Notice

This PR contains a .pr/ directory with temporary PR-specific documents. The directory will be automatically removed when the PR is approved.

Co-authored-by: openhands <openhands@all-hands.dev>
@malhotra5
malhotra5 force-pushed the task-outcome-reporting branch from 2ea3b55 to 54fdb2b Compare August 11, 2026 23:10
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
@malhotra5
malhotra5 marked this pull request as ready for review August 11, 2026 23:59
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: be17960b7846fdfc4bd66ce2fb6dd40bd890f6b3
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/568dcdd4-a2d6-4ad5-991b-fb2477e11f4e

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

all-hands-bot commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Review complete.

This review was performed through OpenHands Cloud Automation. You can log in and view the conversation here.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Taste Rating: Acceptable - The core implementation is simple and the targeted tests pass, but this is not merge-ready because it changes the default FinishTool contract without the repo-required eval evidence and commits noisy prompt transcripts as source artifacts.

[CRITICAL ISSUES]

  • [openhands-sdk/openhands/sdk/tool/builtins/finish.py, Line 104] Testing / Regression Proof: Attaching a default structured response schema to FinishTool is a global agent-behavior change. Per the repository review guide, eval-impacting changes need an eval monitor link or explicit maintainer confirmation before approval. I could not find that in the PR description or comments.

[IMPROVEMENT OPPORTUNITIES]

  • [.pr/task_outcome_missing_mcp_result.md, Line 23] Artifact Hygiene: The committed evidence artifacts include full system/developer prompt transcripts. Keep the evidence concise and sanitized, or move transient local run logs out of the source tree.

[TESTING GAPS]

  • Targeted tests I ran locally passed:
    • uv run pytest tests/sdk/tool/test_finish_task_outcome.py -q (18 passed)
    • uv run pytest tests/sdk/tool/test_response_schema.py -q (36 passed)
  • These unit tests are useful, but they do not replace the eval evidence required for a default agent tool-contract change.

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟡 MEDIUM
    This changes the default schema/instructions exposed to every SDK agent using FinishTool and alters automation callback payload behavior, so regressions could show up as changed agent completion behavior rather than isolated API failures. The implementation itself is localized and covered by targeted tests, and there are no dependency changes.

VERDICT:
Needs rework: Add the required eval evidence/maintainer confirmation and remove or sanitize the committed prompt transcript artifacts before merge.

KEY INSIGHT:
The data model change is sensible, but because FinishTool is global agent plumbing, proving behavior did not regress matters more than adding more unit-level assertions.


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.

This review was generated by an AI agent (OpenHands) on behalf of the user through OpenHands Automation. View conversation

openWorldHint=False,
),
)
return [tool.set_response_schema(FinishTaskOutcomeResponse)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Critical: Attaching a default structured response schema to FinishTool changes the prompt/tool contract for every SDK agent, so this is eval-impacting under the repository's custom review guide. I couldn't find an eval monitor link or maintainer confirmation in the PR description/comments. Please add that evidence before merge; the targeted unit tests are useful but they don't show the global agent-behavior impact.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Will wait for maintainer confirming on whether we'd like to commit this as default behavior. IMHO its probably a good idea, considering the fact that background agents need a more principled mechanism by which they get specify that they're blocked at a given task.

Wrt to automations specifically, we want to wire this ability by default without having to rely on the user to configure the additional response schema. The automation service will also parse the TaskOutcome as we've defined it.

Will run a subset of eval for regression check

{"asctime": "2026-08-11 22:47:14,152", "levelname": "WARNING", "name": "openhands.sdk.conversation.state", "filename": "state.py", "lineno": 516, "message": "No persistence_dir provided; falling back to InMemoryFileStore. EventLog data will not persist across requests."}
{"asctime": "2026-08-11 22:47:14,153", "levelname": "INFO", "name": "openhands.sdk.conversation.state", "filename": "state.py", "lineno": 587, "message": "Created new conversation 34b6287e-1232-4d2d-8200-7c9725207372"}
{"asctime": "2026-08-11 22:47:14,155", "levelname": "INFO", "name": "openhands.sdk.agent.base", "filename": "base.py", "lineno": 568, "message": "Loaded 0 tools from spec"}
System Prompt ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟠 Important: This committed evidence file includes the full agent system/developer prompt, and .pr/example_result.txt has the same problem. That is transient local run output, not SDK source: it bloats the tree and publishes internal operational instructions in a public repository. Please remove these transcript files from the branch, or replace them with a short sanitized artifact that only shows the command and final task outcome/status lines.

@malhotra5 malhotra5 Aug 12, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

.pr folder is automatically removed from the PR prior to merge. the folder is way to publish assets/evidence as part of the PR

@malhotra5
malhotra5 requested a review from VascoSch92 August 12, 2026 00:25
@malhotra5 malhotra5 added the run-eval-50 Runs evaluation on 50 SWE-bench instances label Aug 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Evaluation Triggered

@all-hands-bot

Copy link
Copy Markdown
Collaborator

❌ Evaluation Failed

Evaluation: 31550236875-gpt-5-5-eval
Model: litellm_proxy/openai/gpt-5.5
Benchmark: swebench
Exit Code: 1

Log Tail:

                             'run_instance_modal' collision!                    
                             Overriding existing function                       
                             [swebench.harness.modal_eval.run_evaluat           
                             ion_modal].run_instance_modal with new             
                             function [None].run_instance_modal                 
[benchmarks] modal sitecustomize: patched function timeout to 14400s
[benchmarks] modal sitecustomize: applied runtime debug patch
<frozen runpy>:128: RuntimeWarning: 'swebench.harness.run_evaluation' found in sys.modules after import of package 'swebench.harness', but prior to execution of 'swebench.harness.run_evaluation'; this may result in unpredictable behaviour

Generating test split:   0%|          | 0/500 [00:00<?, ? examples/s]
Generating test split: 100%|██████████| 500/500 [00:00<00:00, 29123.88 examples/s]
[08/12/26 02:13:18] INFO     SWE-Bench evaluation completed    eval_infer.py:177
                             successfully                                       
[08/12/26 02:13:18] ERROR    Script failed: [Errno 2] No such  eval_infer.py:376
                             file or directory:                                 
                             '/workspace/benchmarks/eval_outpu                  
                             ts/princeton-nlp__SWE-bench_Verif                  
                             ied-test/litellm_proxy/openai/gpt                  
                             -5.5_sdk_be17960_maxiter_500_N_li                  
                             tellm_proxy-openai-gpt-5-5/OpenHa                  
                             nds.31550236875-gpt-5-5-eval.json                  
                             '                                                  

@enyst enyst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🐾 (smolpaws, for Engel) — flagging a stale-task_outcome window on conversations that continue past the first finish (e.g. a Slack automation that replies, then the user sends another message and the same conversation resumes).

_task_outcome_recording only ever writes task_outcome (on FinishAction or ConversationErrorEvent) and it's persisted on ConversationState. It's never reset on resume. So:

  1. run 1 finishes → outcome stored
  2. user pings again → run 2 resumes; task_outcome still holds run 1's until run 2 sets its own
  3. if run 2 ends without a finish (a chat-style reply often does), close() ships run 1's outcome — and the automation#329 watchdog fallback reads that same persisted value

reported_at/terminal_reason let a reader detect staleness but nothing enforces it. Cheap fix: clear (or mark in-progress) at run start / on the new user message.

@juanmichelini juanmichelini left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Love the purpose of the PR. Is this backwards compatible or does it introduce breaking changes? Could we document that on the PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-eval-50 Runs evaluation on 50 SWE-bench instances

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants