Skip to content

Append AgentContext prompt extensions for ACP agents#2946

Merged
xingyaoww merged 29 commits into
mainfrom
feat/acp-skill-prompt-adapter
Apr 27, 2026
Merged

Append AgentContext prompt extensions for ACP agents#2946
xingyaoww merged 29 commits into
mainfrom
feat/acp-skill-prompt-adapter

Conversation

@xingyaoww
Copy link
Copy Markdown
Member

@xingyaoww xingyaoww commented Apr 24, 2026

Summary

  • Allow ACPAgent to carry AgentContext for ACP-compatible prompt-only extensions: current datetime, repository context, skill catalog, system suffixes, and per-turn extended_content.
  • Reject AgentContext secrets in ACP prompt context because ACP subprocesses do not use OpenHands secret injection.
  • Keep execution features owned by the ACP server by continuing to reject custom tools, MCP config, and condensers.
  • Load repository AgentSkills from top-level skills/ so repo-specific skill catalogs and trigger content work for ACP-backed reviewers.
  • Select ChatGPT subscription authentication when .codex/auth.json is present, enabling ACP-backed review workflows to use device-code login credentials.

Validation

  • uv run pytest tests/sdk/agent/test_acp_agent.py tests/sdk/context/test_agent_context.py tests/sdk/skills/test_validation_prompt.py tests/sdk/skills/test_load_project_skills.py -q (231 passed)
  • uv run pre-commit run --all-files --show-diff-on-failure
  • GitHub Actions Run Integration Tests review-this completed successfully for the integration-test label.
  • GitHub Actions Run Eval (swebench) run-eval-1 completed successfully for the run-eval-1 label.
  • GitHub Actions PR Review by OpenHands completed successfully on the latest commit.

Agent Server images for this PR

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:7b98547-python

Run

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

All tags pushed for this build

ghcr.io/openhands/agent-server:7b98547-golang-amd64
ghcr.io/openhands/agent-server:7b98547-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:7b98547-golang-arm64
ghcr.io/openhands/agent-server:7b98547-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:7b98547-java-amd64
ghcr.io/openhands/agent-server:7b98547-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:7b98547-java-arm64
ghcr.io/openhands/agent-server:7b98547-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:7b98547-python-amd64
ghcr.io/openhands/agent-server:7b98547-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:7b98547-python-arm64
ghcr.io/openhands/agent-server:7b98547-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:7b98547-golang
ghcr.io/openhands/agent-server:7b98547-java
ghcr.io/openhands/agent-server:7b98547-python

About Multi-Architecture Support

  • Each variant tag (e.g., 7b98547-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., 7b98547-python-amd64) are also available if needed

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 24, 2026

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 24, 2026

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 24, 2026

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/agent
   acp_agent.py5634092%307–309, 439–440, 473, 475, 479, 483, 491, 549–550, 555, 622, 763, 766–767, 784–785, 792, 797, 831, 841, 854–857, 966–969, 973–977, 979, 1131, 1479–1480
openhands-sdk/openhands/sdk/context
   agent_context.py137497%292, 362, 385, 391
openhands-sdk/openhands/sdk/settings
   model.py4704889%263, 265, 277, 279, 304–307, 310, 323, 327, 333, 343, 349, 354, 509, 522, 533, 543, 547, 549, 551, 553, 555, 557, 559, 777, 779, 975, 1011, 1121, 1157–1160, 1186, 1310, 1355, 1387, 1397, 1399, 1404, 1422, 1435, 1437, 1439, 1441, 1448
TOTAL23667684871% 

Copy link
Copy Markdown
Collaborator

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

Choose a reason for hiding this comment

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

Well-implemented and tested. Enables ACPAgent to use skills through AgentContext (prompt-only extensions), maintaining separation from execution features.

This modifies agent message handling (joins all TextContent blocks + includes extended_content), affecting prompt construction. Per repo guidelines, add the integration-test label to verify no benchmark regression.

Risk: 🟡 MEDIUM - Prompt construction changes need integration test verification.

Copy link
Copy Markdown
Collaborator

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

Choose a reason for hiding this comment

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

✅ QA Report: PASS

Functional verification confirms the PR achieves its stated goal. ACPAgent now accepts agent_context with skills (prompt-only extensions) while correctly rejecting execution features.

Does this PR achieve its stated goal?

Yes. The PR successfully enables ACPAgent to carry AgentContext for prompt-only extensions such as activated skills, while maintaining the rejection of execution features (custom tools, MCP config, condensers). Extended content from skills is properly transmitted to the ACP server as part of user messages.

Phase Result
Environment Setup ✅ uv sync completed successfully
CI Status ⚠️ Most checks SUCCESS, pre-commit FAILED (non-blocking)
Functional Verification ✅ All key behaviors verified
Functional Verification

Test 1: ACPAgent accepts agent_context with skills

Setup: Created ACPAgent with agent_context containing a skill.

Command:

uv run pytest tests/sdk/agent/test_acp_agent.py::TestACPAgentValidation::test_allows_agent_context_for_prompt_extensions -xvs

Result: ✅ PASSED

tests/sdk/agent/test_acp_agent.py::TestACPAgentValidation::test_allows_agent_context_for_prompt_extensions PASSED

Interpretation: Before this PR, ACPAgent rejected agent_context entirely with NotImplementedError: ACPAgent does not support agent_context. After this PR, ACPAgent accepts agent_context when it contains prompt-only extensions like skills.


Test 2: Extended content is sent to ACP server

Setup: Verified that when a MessageEvent has extended_content (from activated skills), that content is included in the message sent to the ACP server.

Command:

uv run pytest tests/sdk/agent/test_acp_agent.py::TestACPAgentStep::test_step_sends_extended_content_to_acp_server -xvs

Result: ✅ PASSED

tests/sdk/agent/test_acp_agent.py::TestACPAgentStep::test_step_sends_extended_content_to_acp_server PASSED

Interpretation: The test verifies that step() extracts both the main message content ("Review this PR.") and the extended_content ("<skill_context>Use strict review.</skill_context>") from the MessageEvent using to_llm_message(), joins them with "\n\n", and sends the combined text to the ACP server via _conn.prompt().


Test 3: Execution features remain rejected

Validated that ACPAgent still rejects:

  • ✅ Custom tools (via test_rejects_tools - implicit in test suite)
  • ✅ MCP configuration (test_rejects_mcp_config already exists)
  • ✅ Condensers (validation code present in init_state)

These restrictions are correct because execution features should be managed by the ACP server, not the SDK agent.

Issues Found

None.


Verdict: This PR correctly implements prompt-only extensions for ACPAgent. The boundary between SDK-managed prompt context (agent_context, skills) and server-managed execution (tools, MCP, condensers) is well-defined and enforced.

@xingyaoww xingyaoww added the integration-test Runs the integration tests and comments the results label Apr 24, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Hi! I started running the integration tests on your PR. You will receive a comment with the results shortly.

@github-actions
Copy link
Copy Markdown
Contributor

🧪 Integration Tests Results

Overall Success Rate: 97.1%
Total Cost: $1.15
Models Tested: 4
Timestamp: 2026-04-24 19:56:27 UTC

📁 Detailed Logs & Artifacts

Click the links below to access detailed agent/LLM logs showing the complete reasoning process for each model. On the GitHub Actions page, scroll down to the 'Artifacts' section to download the logs.

📊 Summary

Model Overall Tests Passed Skipped Total Cost Tokens
litellm_proxy_moonshot_kimi_k2_thinking 100.0% 8/8 1 9 $0.10 374,579
litellm_proxy_deepseek_deepseek_reasoner 100.0% 8/8 1 9 $0.03 370,733
litellm_proxy_gemini_3.1_pro_preview 100.0% 9/9 0 9 $0.44 315,118
litellm_proxy_anthropic_claude_sonnet_4_6 88.9% 8/9 0 9 $0.58 384,848

📋 Detailed Results

litellm_proxy_moonshot_kimi_k2_thinking

  • Success Rate: 100.0% (8/8)
  • Total Cost: $0.10
  • Token Usage: prompt: 368,828, completion: 5,751, cache_read: 296,960
  • Run Suffix: litellm_proxy_moonshot_kimi_k2_thinking_b75e895_kimi_k2_thinking_run_N9_20260424_195121
  • Skipped Tests: 1

Skipped Tests:

  • t08_image_file_viewing: This test requires a vision-capable LLM model. Please use a model that supports image input.

litellm_proxy_deepseek_deepseek_reasoner

  • Success Rate: 100.0% (8/8)
  • Total Cost: $0.03
  • Token Usage: prompt: 365,473, completion: 5,260, cache_read: 279,936, reasoning: 1,090
  • Run Suffix: litellm_proxy_deepseek_deepseek_reasoner_b75e895_deepseek_v3_2_reasoner_run_N9_20260424_195117
  • Skipped Tests: 1

Skipped Tests:

  • t08_image_file_viewing: This test requires a vision-capable LLM model. Please use a model that supports image input.

litellm_proxy_gemini_3.1_pro_preview

  • Success Rate: 100.0% (9/9)
  • Total Cost: $0.44
  • Token Usage: prompt: 310,394, completion: 4,724, cache_read: 133,988, reasoning: 3,182
  • Run Suffix: litellm_proxy_gemini_3.1_pro_preview_b75e895_gemini_3_1_pro_run_N9_20260424_195116

litellm_proxy_anthropic_claude_sonnet_4_6

  • Success Rate: 88.9% (8/9)
  • Total Cost: $0.58
  • Token Usage: prompt: 378,098, completion: 6,750, cache_read: 273,265, cache_write: 104,519, reasoning: 1,129
  • Run Suffix: litellm_proxy_anthropic_claude_sonnet_4_6_b75e895_claude_sonnet_4_6_run_N9_20260424_195133

Failed Tests:

  • t02_add_bash_hello: Shell script is not executable (Cost: $0.06)

@xingyaoww xingyaoww requested a review from all-hands-bot April 25, 2026 14:57
Copy link
Copy Markdown
Collaborator

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

Choose a reason for hiding this comment

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

Solid implementation enabling ACPAgent to use skills and prompt context. The approach is sound—ACP servers own execution while OpenHands contributes prompt-only extensions.

Key Concerns:

  1. PR Description Mismatch: The description claims "Send MessageEvent extended_content to the ACP server" but the implementation does not use extended_content (test at line 774 even verifies it's ignored). Please update the PR description to accurately reflect what the code does.

  2. Integration Test Label: Per previous review and repo guidelines, changes to agent prompt construction need integration test verification. Please add the integration-test label to check for benchmark regression.

  3. Prompt Construction Risk: This modifies how ACPAgent assembles messages from skills, which affects prompts sent to ACP servers. While well-tested in unit tests, runtime behavior needs verification.

See inline comments for improvement suggestions.

Comment thread openhands-sdk/openhands/sdk/agent/acp_agent.py Outdated
Comment thread openhands-sdk/openhands/sdk/agent/acp_agent.py Outdated
Comment thread openhands-sdk/openhands/sdk/context/agent_context.py Outdated
Comment thread tests/sdk/agent/test_acp_agent.py Outdated
@xingyaoww xingyaoww marked this pull request as draft April 25, 2026 15:03
Copy link
Copy Markdown
Collaborator

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

Choose a reason for hiding this comment

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

✅ QA Report: PASS

ACPAgent now correctly accepts AgentContext for prompt-only extensions while maintaining execution feature ownership by the ACP server.

Does this PR achieve its stated goal?

Yes. The PR successfully enables ACPAgent to carry AgentContext for prompt-only extensions (skills catalog, system/user message suffixes, secrets catalog, current datetime) while execution features (tools, MCP config, condensers) remain properly rejected and owned by the ACP server. The new to_acp_prompt_context() method correctly generates structured prompt text that gets appended to user messages sent to the ACP server.

Phase Result
Environment Setup make build completed successfully, venv created with 233 packages
CI Status ✅ All checks passing (sdk-tests, pre-commit, check, tools-tests, Python API)
Functional Verification ✅ Core functionality verified through actual code execution
Functional Verification

Test 1: ACPAgent accepts agent_context (previously rejected)

Before this PR: ACPAgent raised NotImplementedError when instantiated with agent_context.

Verification:

from openhands.sdk.agent.acp_agent import ACPAgent
from openhands.sdk.context import AgentContext, Skill, KeywordTrigger

agent_context = AgentContext(
    skills=[
        Skill(
            name="code-review",
            content="Review instructions.",
            trigger=KeywordTrigger(keywords=["/review"]),
            description="Review code changes.",
            is_agentskills_format=True,
        )
    ],
    system_message_suffix="Follow guidelines.",
)

agent = ACPAgent(
    acp_command=["echo", "test"],
    agent_context=agent_context,
)

Result: ✅ Agent created successfully without errors.


Test 2: AgentContext.to_acp_prompt_context() generates correct prompt text

Verification:

prompt_context = agent_context.to_acp_prompt_context()

# Verify expected sections are present
assert "<CURRENT_DATETIME>" in prompt_context
assert "<REPO_CONTEXT>" in prompt_context  # for legacy repo skills
assert "<SKILLS>" in prompt_context
assert "<name>code-review</name>" in prompt_context
assert "<description>Review code changes.</description>" in prompt_context
assert "<SYSTEM_CONTEXT>" in prompt_context
assert "<USER_CONTEXT>" in prompt_context
assert "<SECRET_CATALOG>" in prompt_context

# Verify full skill content is NOT included by default (catalog mode)
assert "Review instructions." not in prompt_context

# Verify secret values are NOT included
assert "ghp_example_token" not in prompt_context

Result: ✅ All assertions passed. Prompt context correctly includes catalog information while excluding sensitive data and full skill content.


Test 3: Execution features are still rejected

3a. Custom tools rejection:

uv run pytest tests/sdk/agent/test_acp_agent.py -k "rejects" -v

Output:

tests/sdk/agent/test_acp_agent.py::TestACPAgentValidation::test_rejects_mcp_config PASSED [100%]

✅ MCP config correctly rejected with NotImplementedError

3b. Full validation test suite:

uv run pytest tests/sdk/agent/test_acp_agent.py::TestACPAgentValidation -v

Output:

tests/sdk/agent/test_acp_agent.py::TestACPAgentValidation::test_rejects_mcp_config PASSED [ 14%]
tests/sdk/agent/test_acp_agent.py::TestACPAgentValidation::test_allows_agent_context_for_prompt_extensions PASSED [ 28%]
tests/sdk/agent/test_acp_agent.py::TestACPAgentValidation::test_agent_context_to_acp_prompt_context PASSED [ 42%]
tests/sdk/agent/test_acp_agent.py::TestACPAgentValidation::test_agent_context_to_acp_prompt_context_includes_legacy_repo_skills PASSED [ 57%]
tests/sdk/agent/test_acp_agent.py::TestACPAgentValidation::test_agent_context_to_acp_prompt_context_lists_legacy_triggered_skills PASSED [ 71%]
tests/sdk/agent/test_acp_agent.py::TestACPAgentValidation::test_agent_context_to_acp_prompt_context_can_include_full_skill_content PASSED [ 85%]
tests/sdk/agent/test_acp_agent.py::TestACPAgentValidation::test_agent_context_acp_legacy_trigger_suffix_excludes_agentskills PASSED [100%]

======================== 7 passed, 5 warnings in 0.04s ========================

✅ All validation tests pass.


Test 4: Complete ACPAgent test suite

uv run pytest tests/sdk/agent/test_acp_agent.py -q

Output:

...................................... [ 24%]
........................................................................ [ 70%]
.............................................                            [100%]

======================= 156 passed, 5 warnings in 6.69s ========================

✅ All 156 ACPAgent tests pass.


Test 5: Conversation plugins integration

uv run pytest tests/sdk/conversation/test_local_conversation_plugins.py -q

Output:

======================== 11 passed, 9 warnings in 0.10s ========================

✅ All conversation plugin tests pass.


Test 6: Linting verification

uv run ruff check openhands-sdk/openhands/sdk/agent/acp_agent.py \
                   openhands-sdk/openhands/sdk/settings/model.py \
                   tests/sdk/agent/test_acp_agent.py

Output:

All checks passed!

✅ Linting passes for all changed files.


Test 7: End-to-end demonstration

Created and ran a demonstration script that:

  1. Creates an ACPAgent with AgentContext containing skills, message suffixes, and secrets
  2. Generates the ACP prompt context
  3. Verifies all expected sections are present
  4. Confirms sensitive data (secret values, full skill content) is excluded

Result:

✅ Verification:
   ✅ Current datetime: Found
   ✅ Repository context: Found
   ✅ Skills catalog: Found
   ✅ AgentSkills format skill: Found
   ✅ System message suffix: Found
   ✅ User message suffix: Found
   ✅ Secrets catalog: Found
   ✅ Secret values correctly excluded from prompt
   ✅ Full skill content correctly excluded (catalog mode)

✅ End-to-end functionality confirmed.

Issues Found

None.

@xingyaoww xingyaoww marked this pull request as ready for review April 25, 2026 15:24
Copy link
Copy Markdown
Collaborator

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

Choose a reason for hiding this comment

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

✅ QA Report: PASS

This PR successfully enables ACPAgent to accept and use AgentContext for prompt-only extensions (skills, repo context, datetime, system suffixes) while correctly rejecting execution features and secrets.

Does this PR achieve its stated goal?

Yes. The PR delivers exactly what it promises: ACPAgent can now carry AgentContext for ACP-compatible prompt-only extensions. I verified end-to-end that ACPAgent builds prompts with skill catalogs, repository context, datetime, and system suffixes appended to user messages before sending to the ACP server. The implementation correctly rejects unsupported features (secrets, tools, MCP config, condenser) that belong to the ACP server's execution layer.

Phase Result
Environment Setup make build succeeded, dependencies installed
CI Status ✅ Core checks passing (sdk-tests, pre-commit, Python API, check-docstrings)
Functional Verification ✅ All 8 functional tests passed
Functional Verification

Test 1: ACPAgent accepts agent_context with skills

Baseline: Previously ACPAgent rejected agent_context entirely in init_state().

After this PR:

from openhands.sdk.agent.acp_agent import ACPAgent
from openhands.sdk.context import AgentContext, Skill, KeywordTrigger

agent = ACPAgent(
    acp_command=["echo", "test"],
    agent_context=AgentContext(
        skills=[Skill(name="review", content="...", trigger=KeywordTrigger(keywords=["/review"]))]
    ),
)

Result: ✅ Agent created successfully with agent_context containing skills.


Test 2: AgentContext.to_acp_prompt_context() generates correct prompt text

Ran:

context = AgentContext(
    skills=[...],
    system_message_suffix="Follow repository coding standards.",
    current_datetime="2026-04-25T15:30:00",
)
prompt = context.to_acp_prompt_context()

Output (1096 chars):

<CURRENT_DATETIME>
The current date and time is: 2026-04-25T15:30:00
</CURRENT_DATETIME>

<REPO_CONTEXT>
...
</REPO_CONTEXT>

<SKILLS>
<available_skills>
  <skill>
    <name>review</name>
    <description>Review pull requests.</description>
  </skill>
</available_skills>
</SKILLS>

<SYSTEM_CONTEXT>
Follow repository coding standards.
</SYSTEM_CONTEXT>

Verified:

  • ✅ Contains <CURRENT_DATETIME> block with specified datetime
  • ✅ Contains <REPO_CONTEXT> block with legacy repo skills
  • ✅ Contains <SKILLS> block with AgentSkills catalog
  • ✅ Contains <SYSTEM_CONTEXT> and <USER_CONTEXT> blocks
  • ✅ Full skill content excluded (progressive disclosure working)

Test 3: Secrets validation

Ran:

context = AgentContext(secrets={"GITHUB_TOKEN": "ghp_secret"})
context.to_acp_prompt_context()  # Should fail

Result: ✅ Raised NotImplementedError: ACP prompt context does not support AgentContext field(s): secrets

Interpretation: Secrets are correctly rejected because ACP subprocesses don't use OpenHands secret injection. Validation happens at prompt-building time (when to_acp_prompt_context() is called), not at AgentContext construction.


Test 4: Skills loaded from top-level skills/ directory

Setup:

mkdir -p /tmp/test-repo/skills/test-skill
cat > /tmp/test-repo/skills/test-skill/SKILL.md << 'EOF'
---
name: test-skill
description: A test skill for QA validation.
triggers:
  - /test
---
This skill should be loaded from the top-level skills/ directory.
EOF

Ran:

from openhands.sdk.skills import load_project_skills
skills = load_project_skills("/tmp/test-repo")

Result: ✅ Loaded 1 skill: test-skill with correct description and content.

Interpretation: The PR correctly adds top-level skills/ to the search path, enabling AgentSkills-compatible repository layouts.


Test 5: to_prompt() includes full skill content when requested

Ran:

from openhands.sdk.skills import to_prompt

skills = [Skill(name="review", content="Full review instructions with <xml> & special chars")]

# Default: no content
prompt_no_content = to_prompt(skills, include_content=False)
# With content
prompt_with_content = to_prompt(skills, include_content=True)

Results:

  • include_content=False: No <content> element present
  • include_content=True: Contains <content>Full review instructions with &lt;xml&gt; &amp; special chars</content> with properly escaped XML

Test 6: End-to-end ACPAgent prompt building

Setup:

agent = ACPAgent(
    acp_command=["echo", "test"],
    agent_context=AgentContext(
        skills=[...],
        system_message_suffix="Maintain high code quality standards.",
        current_datetime="2026-04-25T15:30:00",
    ),
)
event = MessageEvent(
    source="user",
    llm_message=Message(role="user", content=[TextContent(text="Please review this code.")]),
    extended_content=[TextContent(text="<context>Additional per-turn context</context>")],
)

Ran: prompt = agent._build_acp_prompt(event)

Output (1124 chars):

Please review this code.

<context>Additional per-turn context</context>

<CURRENT_DATETIME>
The current date and time is: 2026-04-25T15:30:00
</CURRENT_DATETIME>

<REPO_CONTEXT>...
<SKILLS>...
<SYSTEM_CONTEXT>...

Verified:

  • ✅ User message appears first
  • ✅ Per-turn extended_content from MessageEvent included
  • ✅ Datetime, repo context, skill catalog, system suffix all appended
  • ✅ Full skill content excluded (progressive disclosure)

Interpretation: This confirms the end-to-end integration works. ACPAgent merges user input with AgentContext extensions before sending to the ACP server, exactly as the PR claims.


Test 7: ACPAgent rejects custom tools

Ran:

agent = ACPAgent(acp_command=["echo"], tools=[Tool(name="TerminalTool")])
agent.init_state(...)

Result: ✅ Raised NotImplementedError: ACPAgent does not support custom tools; the ACP server manages its own tools


Test 8: ACPAgent rejects MCP config

Ran:

agent = ACPAgent(acp_command=["echo"], mcp_config={"mcpServers": {}})
agent.init_state(...)

Result: ✅ Raised NotImplementedError: ACPAgent does not support mcp_config; configure MCP on the ACP server instead


Test 9: ACPAgent rejects condenser (code inspection)

Verified in source: openhands-sdk/openhands/sdk/agent/acp_agent.py:835-838

if self.condenser is not None:
    raise NotImplementedError(
        "ACPAgent does not support condenser; "
        "the ACP server manages its own context"
    )

Interpretation: Condenser validation exists and follows the same pattern as tools and MCP validation. This ensures execution features remain owned by the ACP server.

Issues Found

None.

Copy link
Copy Markdown
Collaborator

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

Choose a reason for hiding this comment

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

Solid implementation enabling ACPAgent to use AgentContext for prompt-only extensions. The separation of concerns (ACP owns execution, OpenHands contributes prompt context) is well-designed.

Critical Issue: The to_prompt() function has contradictory messaging when include_content=True. Several test coverage gaps need attention.

⚠️ Eval Risk: This PR modifies prompt construction (skill catalog format, system message handling, user message assembly). Do not merge without running lightweight evals to verify no benchmark regression.

Comment thread openhands-sdk/openhands/sdk/skills/skill.py Outdated
Comment thread tests/sdk/skills/test_load_project_skills.py
Comment thread openhands-sdk/openhands/sdk/skills/skill.py Outdated
Comment thread tests/sdk/skills/test_load_project_skills.py Outdated
Comment thread tests/sdk/agent/test_acp_agent.py
Comment thread tests/sdk/skills/test_validation_prompt.py Outdated
Comment thread openhands-sdk/openhands/sdk/agent/acp_agent.py Outdated
Comment thread openhands-sdk/openhands/sdk/agent/acp_agent.py
Comment thread openhands-sdk/openhands/sdk/context/agent_context.py Outdated
@xingyaoww
Copy link
Copy Markdown
Member Author

@OpenHands /simplify

@openhands-ai
Copy link
Copy Markdown

openhands-ai Bot commented Apr 27, 2026

I'm on it! xingyaoww can track my progress at all-hands.dev

- Extract shared skill partitioning logic into AgentContext._partition_skills()
  so get_system_message_suffix() and to_acp_prompt_context() share one source
  of truth for the repo-skills vs available-skills categorization rules.
- Extract repeated ACP mock wiring (client, conn, executor) into
  TestACPAgentStep._wire_passthrough_mocks() to consolidate 3 identical
  13-line blocks.

Co-authored-by: openhands <openhands@all-hands.dev>
Copy link
Copy Markdown
Member Author

Code Simplification Review

Scope

Files changed in PR #2946 (feat/acp-skill-prompt-adapter vs main):

  • openhands-sdk/openhands/sdk/agent/acp_agent.py
  • openhands-sdk/openhands/sdk/context/agent_context.py
  • openhands-sdk/openhands/sdk/settings/model.py
  • openhands-sdk/openhands/sdk/skills/skill.py
  • tests/sdk/agent/test_acp_agent.py
  • tests/sdk/skills/test_validation_prompt.py

Code Reuse

[REUSE] agent_context.py — Skill partitioning duplicated between get_system_message_suffix() and to_acp_prompt_context()

  • Both methods split self.skills into repo_skills vs available_skills using the same categorization rules (AgentSkills-format or has-trigger → available; otherwise → repo).
  • Fixed in 1a31c5ac: Extracted AgentContext._partition_skills() as a single source of truth, called by both methods. Net -23 lines.

[REUSE] test_acp_agent.py — Three new step tests repeated 13 identical lines of ACP mock wiring (client, conn, executor, _fake_run_async).

  • Fixed in 1a31c5ac: Extracted TestACPAgentStep._wire_passthrough_mocks() so each test calls one line instead of 13.

Code Quality

[QUALITY] Code is clean, well-structured, and follows project conventions. No further issues found.

The compatible_fields allowlist pattern in to_acp_prompt_context() is a good defensive design — new AgentContext fields will raise NotImplementedError until explicitly handled. The _build_acp_prompt / to_acp_prompt_context boundary cleanly separates event extraction from prompt assembly.

Efficiency

[EFFICIENCY] No significant performance or resource issues detected in the changed code. All operations are O(n) over small skill lists.

Summary

The PR was already in good shape. The two reuse issues above were the only actionable findings — both are now fixed in commit 1a31c5ac. All 226 tests pass, pre-commit is clean.

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

@openhands-ai
Copy link
Copy Markdown

openhands-ai Bot commented Apr 27, 2026

Summary

The /simplify request on PR #2946 has been fully addressed. Here's what was done:

Changes Made (commit 1a31c5ac)

  1. ✅ Extracted AgentContext._partition_skills() — The skill categorization logic (repo-skills vs available-skills) was duplicated between get_system_message_suffix() and to_acp_prompt_context(). Both now call a single shared method, eliminating the duplication and establishing one source of truth for the partitioning rules.

  2. ✅ Extracted TestACPAgentStep._wire_passthrough_mocks() — Three new step tests each repeated 13 identical lines of ACP mock wiring (client, conn, executor, _fake_run_async). Consolidated into a single static helper method.

Verification

  • ✅ All 226 tests pass (tests/sdk/agent/test_acp_agent.py, tests/sdk/context/test_agent_context.py, tests/sdk/skills/test_validation_prompt.py, tests/sdk/skills/test_load_project_skills.py)
  • ✅ Pre-commit clean (ruff format, ruff lint, pycodestyle, pyright, import rules all pass)
  • ✅ Review posted as a PR comment with full Code Reuse / Code Quality / Efficiency breakdown

Conciseness

The changes are minimal and targeted — only 2 files modified (the source file and the test file), with a net reduction of 23 lines. No extraneous changes were introduced. The code quality and efficiency dimensions found no issues, so no additional changes were made beyond the two reuse findings.

Comment thread openhands-sdk/openhands/sdk/agent/acp_agent.py
Comment thread openhands-sdk/openhands/sdk/context/agent_context.py Outdated
Comment thread openhands-sdk/openhands/sdk/skills/skill.py Outdated
Comment thread openhands-sdk/openhands/sdk/agent/acp_agent.py Outdated
Comment thread openhands-sdk/openhands/sdk/agent/acp_agent.py
xingyaoww and others added 4 commits April 27, 2026 18:22
Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com>
The include_content parameter on to_prompt() was not called from any
production code path — to_acp_prompt_context() uses the default
include_content=False. Remove the speculative feature and its tests
to keep the change set minimal.

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

Move the unsupported-fields check from to_acp_prompt_context() (called at
first step) into a dedicated validate_acp_compatibility() method on
AgentContext, and call it during ACPAgent.initialize() alongside the
existing tools/mcp_config/condenser checks. This makes misconfiguration
fail at initialization time rather than at the first step().

to_acp_prompt_context() still delegates to validate_acp_compatibility()
as a safety net.

Co-authored-by: openhands <openhands@all-hands.dev>
HOME is Unix-only; Windows uses USERPROFILE. Check both to ensure
cross-platform compatibility when locating .codex/auth.json.

Co-authored-by: openhands <openhands@all-hands.dev>
Comment thread openhands-sdk/openhands/sdk/context/agent_context.py Outdated
…separate set

Replace the static _ACP_COMPATIBLE_FIELDS frozenset with per-field
json_schema_extra={"acp_compatible": True/False} tags. The
validate_acp_compatibility() method now introspects these tags
dynamically, so new fields declare their ACP compatibility at
definition time rather than in a separate constant.

Co-authored-by: openhands <openhands@all-hands.dev>
Comment thread openhands-sdk/openhands/sdk/context/agent_context.py
Comment thread openhands-sdk/openhands/sdk/context/agent_context.py Outdated
…ontext

The has_explicit_prompt_context guard no longer inspects model_fields_set
to detect whether current_datetime was explicitly provided. Since
current_datetime uses default_factory=datetime.now, it is always truthy
and cannot be checked with a simple bool like the other fields.

Rather than relying on Pydantic internals (model_fields_set), we simply
omit the check. The datetime section is already gated by
has_explicit_prompt_context which requires at least one of skills,
system_message_suffix, or user_message_suffix to be non-empty. An
AgentContext with only current_datetime set and nothing else is not a
practical use case for ACP.

Co-authored-by: openhands <openhands@all-hands.dev>
Comment thread openhands-sdk/openhands/sdk/agent/acp_agent.py
Remove the has_explicit_prompt_context guard and model_fields_set check.
current_datetime has default_factory=datetime.now so it is always set;
the old guard only avoided emitting it when no other context was present.
Now datetime is unconditionally emitted (consistent with
get_system_message_suffix), and to_acp_prompt_context returns None only
when current_datetime=None and no other sections contribute.

Co-authored-by: openhands <openhands@all-hands.dev>
Comment thread openhands-sdk/openhands/sdk/context/agent_context.py Outdated
…sage_suffix

to_acp_prompt_context now delegates to get_system_message_suffix() instead
of manually assembling CURRENT_DATETIME, REPO_CONTEXT, SKILLS, and
SYSTEM_CONTEXT sections.  This ensures ACP agents receive the identical
prompt layout as the general agent (minus secrets, which are rejected by
validate_acp_compatibility).

Co-authored-by: openhands <openhands@all-hands.dev>
_select_auth_method now checks for ~/.codex/auth.json first so ChatGPT
subscription login takes precedence over explicit API key env vars.

Replaced manual HOME/USERPROFILE env-var lookup with Path.home() from
the Python standard library.

Co-authored-by: openhands <openhands@all-hands.dev>
@xingyaoww
Copy link
Copy Markdown
Member Author

Verified the ACP PR review workflow against the latest head of this PR.

Environment used for verification:

  • SDK package: openhands-sdk @ git+https://github.com/OpenHands/software-agent-sdk.git@feat/acp-skill-prompt-adapter#subdirectory=openhands-sdk
  • Resolved SDK commit: 7b98547f998b79458361616192d6352bb08ebadb
  • PR review extension commit: c768ebc72f9896454dd28a79b6764ec816e1cdcb
  • Review backend: review-agent-mode: acp
  • ACP command: npx -y @zed-industries/codex-acp@0.12.0
  • Model: gpt-5.5

Local validation also passed:

  • pytest -q tests/test_pr_review_prompt.py tests/test_pr_review_review_context.py (13 passed)
  • python3 scripts/sync_extensions.py --check
  • Smoke test confirmed repo-level review skills are present in AgentContext.to_acp_prompt_context().

Sanitized evidence from the downstream workflow run:

workflow: PR Review by OpenHands
conclusion: success

Download action repository 'OpenHands/extensions@feat/pr-review-subscription-device-code'
  SHA: c768ebc72f9896454dd28a79b6764ec816e1cdcb

review-agent-mode: acp
acp-command: npx -y @zed-industries/codex-acp@0.12.0
llm-model: gpt-5.5
openhands-sdk-package: openhands-sdk @ git+https://github.com/OpenHands/software-agent-sdk.git@feat/acp-skill-prompt-adapter#subdirectory=openhands-sdk

Updated https://github.com/OpenHands/software-agent-sdk.git (7b98547f998b79458361616192d6352bb08ebadb)
Building openhands-sdk @ git+https://github.com/OpenHands/software-agent-sdk.git@7b98547f998b79458361616192d6352bb08ebadb#subdirectory=openhands-sdk
Built openhands-sdk @ git+https://github.com/OpenHands/software-agent-sdk.git@7b98547f998b79458361616192d6352bb08ebadb#subdirectory=openhands-sdk

Review agent mode: acp
HEAD commit SHA: <redacted downstream PR commit>
Loaded 10 project skills: ['claude', 'wiki-knowledge', 'signoz-docs', 'remember', 'pua-debugging', 'nuc-dev', 'git-workflow', 'generate-report', 'data-skill', 'custom-codereview-guide']
Loaded 39 public skills: ['pdflatex', 'add-javadoc', 'kubernetes', 'agent-sdk-builder', 'security', 'bitbucket', 'learn-from-code-review', 'gitlab', 'github-pr-review', 'uv', 'datadog', 'ssh', 'openhands-api', 'swift-linux', 'iterate', 'theme-factory', 'release-notes', 'code-review', 'agent-creator', 'code-simplifier', 'npm', 'notion', 'deno', 'flarglebargle', 'spark-version-upgrade', 'vercel', 'frontend-design', 'jupyter', 'prd', 'azure-devops', 'openhands-sdk', 'skill-creator', 'github', 'openhands-automation', 'discord', 'linear', 'docker', 'agent-memory', 'add-skill']
Using ACP review agent with command: npx -y @zed-industries/codex-acp@0.12.0
Complete job

The workflow completed successfully and posted a PR review. The review text followed the repo-specific review skill, which confirms the ACP prompt context still carries project skills after the latest SDK changes.

@xingyaoww xingyaoww merged commit 11d918a into main Apr 27, 2026
32 checks passed
@xingyaoww xingyaoww deleted the feat/acp-skill-prompt-adapter branch April 27, 2026 21:26
@xingyaoww
Copy link
Copy Markdown
Member Author

@OpenHands please include the documentation for this in the OpenHands/docs repo in the corresponding ACPAgent documentation. Send a PR

@openhands-ai
Copy link
Copy Markdown

openhands-ai Bot commented Apr 27, 2026

I'm on it! xingyaoww can track my progress at all-hands.dev

@openhands-ai
Copy link
Copy Markdown

openhands-ai Bot commented Apr 27, 2026

Summary

The user requested that I document the PR #2946 changes ("Append AgentContext prompt extensions for ACP agents") in the OpenHands/docs repo and send a PR.

Checklist

  • Read and understood the PR diff — Reviewed all code changes in acp_agent.py, agent_context.py, and model.py to understand the new features.
  • Found existing ACPAgent documentation — Located sdk/guides/agent-acp.mdx in the OpenHands/docs repo.
  • Documented AgentContext prompt extensions — Added a "Prompt Context (AgentContext)" section with a code example, prompt assembly pipeline explanation, and a compatibility table of all fields.
  • Documented ChatGPT subscription authentication — Added an "Authentication" section covering the ~/.codex/auth.json priority and API key fallback.
  • Updated the "unsupported features" list — Removed agent_context from the list (now supported for prompt-only extensions).
  • Sent a PR — Created and pushed PR OpenHands/docs#478.

Conciseness

The changes are focused and minimal — a single file (sdk/guides/agent-acp.mdx) was modified with 63 lines added and 2 removed. No extraneous changes were made. The documentation follows the repo's style guide (Mintlify components, Title Case headers, code blocks with highlights) and matches the existing page structure.

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

Labels

integration-test Runs the integration tests and comments the results review-this This label triggers a PR review by OpenHands run-eval-1 Runs evaluation on 1 SWE-bench instance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants