Skip to content
Draft
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
11 changes: 7 additions & 4 deletions examples/01_standalone_sdk/03_activate_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
AgentContext,
Conversation,
Event,
ExtensionConfig,
LLMConvertibleEvent,
get_logger,
)
Expand Down Expand Up @@ -87,9 +88,6 @@
system_message_suffix="Always finish your response with the word 'yay!'",
# user_message_suffix is appended to each user message
user_message_suffix="The first character of your response should be 'I'",
# You can also enable automatic load skills from
# public registry at https://github.com/OpenHands/extensions
load_public_skills=True,
)

# Agent
Expand All @@ -103,8 +101,13 @@ def conversation_callback(event: Event):
llm_messages.append(event.to_llm_message())


# ExtensionConfig controls loading of extensions (skills, plugins, hooks)
# from well-known locations at the conversation level.
conversation = Conversation(
agent=agent, callbacks=[conversation_callback], workspace=cwd
agent=agent,
callbacks=[conversation_callback],
workspace=cwd,
extension_config=ExtensionConfig(load_public_extensions=True),
)

print("=" * 100)
Expand Down
2 changes: 0 additions & 2 deletions examples/05_skills_and_plugins/01_loading_agentskills/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@
# Create agent context with loaded skills
agent_context = AgentContext(
skills=list(agent_skills.values()),
# Disable public skills for this demo to keep output focused
load_public_skills=False,
)

# Create agent with tools so it can read skill resources
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,10 +472,8 @@ async def start(self):
self.stored.agent.model_dump(context={"expose_secrets": True}),
)

# Create LocalConversation with plugins and hook_config.
# Plugins are loaded lazily on first run()/send_message() call.
# Hook execution semantics: OpenHands runs hooks sequentially with early-exit
# on block (PreToolUse), unlike Claude Code's parallel execution model.
# Extensions (plugins, hooks, skills) are loaded lazily on first
# run()/send_message() call via ExtensionConfig.resolve().

# Create and store callback wrapper to allow flushing pending events
self._callback_wrapper = AsyncCallbackWrapper(
Expand All @@ -485,6 +483,7 @@ async def start(self):
conversation = LocalConversation(
agent=agent,
workspace=workspace,
extension_config=self.stored.extension_config,
plugins=self.stored.plugins,
persistence_dir=str(self.conversations_dir),
conversation_id=self.stored.id,
Expand Down
2 changes: 2 additions & 0 deletions openhands-sdk/openhands/sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from openhands.sdk.conversation.conversation_stats import ConversationStats
from openhands.sdk.event import Event, HookExecutionEvent, LLMConvertibleEvent
from openhands.sdk.event.llm_convertible import MessageEvent
from openhands.sdk.extensions.config import ExtensionConfig
from openhands.sdk.io import FileStore, LocalFileStore
from openhands.sdk.llm import (
LLM,
Expand Down Expand Up @@ -134,6 +135,7 @@
"ConversationExecutionStatus",
"ConversationCallbackType",
"Event",
"ExtensionConfig",
"LLMConvertibleEvent",
"AgentContext",
"LLMSummarizingCondenser",
Expand Down
42 changes: 41 additions & 1 deletion openhands-sdk/openhands/sdk/context/agent_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
to_prompt,
)
from openhands.sdk.skills.skill import DEFAULT_MARKETPLACE_PATH
from openhands.sdk.utils.deprecation import warn_deprecated


logger = get_logger(__name__)
Expand Down Expand Up @@ -59,13 +60,21 @@ class AgentContext(BaseModel):
)
load_user_skills: bool = Field(
default=False,
deprecated=(
"Deprecated since v1.18.0; will be removed in v1.23.0. "
"Use ExtensionConfig(load_user_extensions=True) on Conversation."
),
description=(
"Whether to automatically load user skills from ~/.openhands/skills/ "
"and ~/.openhands/microagents/ (for backward compatibility). "
),
)
load_public_skills: bool = Field(
default=False,
deprecated=(
"Deprecated since v1.18.0; will be removed in v1.23.0. "
"Use ExtensionConfig(load_public_extensions=True) on Conversation."
),
description=(
"Whether to automatically load skills from the public OpenHands "
"skills repository at https://github.com/OpenHands/extensions. "
Expand All @@ -74,6 +83,10 @@ class AgentContext(BaseModel):
)
marketplace_path: str | None = Field(
default=DEFAULT_MARKETPLACE_PATH,
deprecated=(
"Deprecated since v1.18.0; will be removed in v1.23.0. "
"Use ExtensionConfig(marketplace_path=...) on Conversation."
),
description=(
"Relative marketplace JSON path within the public skills repository. "
"Set to None to load all public skills without marketplace filtering."
Expand Down Expand Up @@ -114,10 +127,37 @@ def _validate_skills(cls, v: list[Skill], _info):

@model_validator(mode="after")
def _load_auto_skills(self):
"""Load user and/or public skills if enabled."""
"""Load user and/or public skills if enabled.

.. deprecated:: 1.18.0
Use ``ExtensionConfig(load_user_extensions=...,
load_public_extensions=...)`` on ``Conversation`` instead.
Will be removed in v1.23.0.
"""
if not self.load_user_skills and not self.load_public_skills:
return self

_details = (
"Use ExtensionConfig(load_user_extensions=..., "
"load_public_extensions=...) on Conversation instead."
)
if self.load_user_skills:
warn_deprecated(
"AgentContext.load_user_skills",
deprecated_in="1.18.0",
removed_in="1.23.0",
details=_details,
stacklevel=3,
)
if self.load_public_skills:
warn_deprecated(
"AgentContext.load_public_skills",
deprecated_in="1.18.0",
removed_in="1.23.0",
details=_details,
stacklevel=3,
)

auto_skills = load_available_skills(
work_dir=None,
include_user=self.load_user_skills,
Expand Down
6 changes: 6 additions & 0 deletions openhands-sdk/openhands/sdk/conversation/conversation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
ConversationVisualizerBase,
DefaultConversationVisualizer,
)
from openhands.sdk.extensions.config import ExtensionConfig
from openhands.sdk.hooks import HookConfig
from openhands.sdk.logger import get_logger
from openhands.sdk.plugin import PluginSource
Expand Down Expand Up @@ -63,6 +64,7 @@ def __new__(
agent: AgentBase,
*,
workspace: str | Path | LocalWorkspace = "workspace/project",
extension_config: ExtensionConfig | None = None,
plugins: list[PluginSource] | None = None,
persistence_dir: str | Path | None = None,
conversation_id: ConversationID | None = None,
Expand All @@ -88,6 +90,7 @@ def __new__(
agent: AgentBase,
*,
workspace: RemoteWorkspace,
extension_config: ExtensionConfig | None = None,
plugins: list[PluginSource] | None = None,
conversation_id: ConversationID | None = None,
callbacks: list[ConversationCallbackType] | None = None,
Expand All @@ -111,6 +114,7 @@ def __new__(
agent: AgentBase,
*,
workspace: str | Path | LocalWorkspace | RemoteWorkspace = "workspace/project",
extension_config: ExtensionConfig | None = None,
plugins: list[PluginSource] | None = None,
persistence_dir: str | Path | None = None,
conversation_id: ConversationID | None = None,
Expand Down Expand Up @@ -168,6 +172,7 @@ def __new__(

return RemoteConversation(
agent=agent,
extension_config=extension_config,
plugins=plugins,
conversation_id=conversation_id,
callbacks=callbacks,
Expand All @@ -185,6 +190,7 @@ def __new__(

return LocalConversation(
agent=agent,
extension_config=extension_config,
plugins=plugins,
conversation_id=conversation_id,
callbacks=callbacks,
Expand Down
Loading
Loading