Skip to content
Closed
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
63 changes: 63 additions & 0 deletions docs/harness-sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,69 @@ handlers always receive a validated model instance; they never receive `None`.
Tools can attach typed context to their result by setting `context_type` and, if
needed, registering a schema and formatter with `register_context()`.

Tools are inherited by spawned sub-agents by default. Set
`inheritance=ToolInheritancePolicy.DO_NOT_INHERIT` when a tool must remain on
the current agent.

## Scoped Code Mode

`create_codemode_tool()` exposes generated Python as one model tool while making
only an explicit, immutable tuple of capabilities callable from that Python.
The factory never discovers tools from the harness, so capabilities can remain
hidden from the model's top-level tool list.

```python
from hyperforge.harness_sdk import (
AgentHarness,
CodeModeCapability,
create_codemode_tool,
)

code_mode = create_codemode_tool(
capabilities=(CodeModeCapability(weather),),
)
agent = AgentHarness(
model="your-model",
model_client=model_client,
tools=[code_mode],
)
```

The generated tool description includes each capability's description and
argument JSON Schema. Capability calls use normal `HarnessTool` input and output
validation and count toward ordinary harness tool-call usage. The optional
`question` Code Mode input is available to generated code as `question`.

By default, capability results are projected through the tool's formatted
model-facing context. Supply a custom `result_adapter` for another safe
projection, or opt into the complete JSON-mode Pydantic output with
`raw_codemode_result_adapter`. Every adapter result is normalized through a
strict JSON round trip before generated code receives it, so tuples become
lists and non-JSON or non-finite values are rejected consistently across
runners. Adapters must not return Pydantic models, including models nested in
containers, or dictionaries containing the reserved `__model__` key.
Values passed to scoped `output(...)` use the same normalization and rejection
rules. Generated code must call `output(value)` exactly once; a missing or
repeated call fails the invocation.

Capability names must be unique ASCII public Python identifiers, cannot use
reserved worker names, and cannot expose an `agent_id` input field. Scoped Code
Mode tools default to `ToolInheritancePolicy.DO_NOT_INHERIT`. If the outer Code
Mode tool is explicitly configured with `INHERIT`, all of its capabilities must
also be inheritable; the factory rejects combinations that would bypass a
capability's `DO_NOT_INHERIT` policy.

For tests, `runner=` accepts a `CodeModeRunner`; its `run()` method receives the
worker request and capability dispatch callback. Without an injected runner,
the factory uses the existing `SandboxRunner.remote` when `SANDBOX_SOCKET` is
configured and the existing isolated process otherwise. Runtime and memory
behavior continues to use the existing sandbox implementation and
`UsageLimits`; this foundation adds no separate sandbox limits.

The existing exported `codemode` tool remains available for compatibility and
continues to discover registered harness tools. Prefer the scoped factory when
the generated program should have an explicit capability boundary.

## Usage Limits

All limits are disabled by default. Configure only the limits needed by the
Expand Down
4 changes: 4 additions & 0 deletions hyperforge/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## Unreleased

- Added explicit scoped Code Mode capabilities and child-tool inheritance policy.

## 1.0.0

- Initial version
23 changes: 21 additions & 2 deletions hyperforge/src/hyperforge/harness_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,19 @@
HarnessToolCall,
)
from .storage import HarnessStorageProtocol, InMemoryHarnessStorage
from .tools import HarnessTool, ToolCallContext, tool
from .tools.codemode import CodemodeInput, CodemodeOutput, codemode
from .tools import HarnessTool, ToolCallContext, ToolInheritancePolicy, tool
from .tools.codemode import (
CodeModeCapability,
CodeModeDispatch,
CodemodeInput,
CodemodeOutput,
CodeModeResultAdapter,
CodeModeRunner,
codemode,
context_codemode_result_adapter,
create_codemode_tool,
raw_codemode_result_adapter,
)
from .usage import HarnessUsage, UsageLimitExceeded, UsageLimits

__all__ = [
Expand All @@ -56,6 +67,10 @@
"ChatCompletionToolCallDelta",
"ChatCompletionToolCallFunctionDelta",
"ChatCompletionUsage",
"CodeModeCapability",
"CodeModeDispatch",
"CodeModeResultAdapter",
"CodeModeRunner",
"CodemodeInput",
"CodemodeOutput",
"HarnessAgentToTool",
Expand All @@ -79,14 +94,18 @@
"NucliaChatCompletionsClient",
"NucliaChatCompletionsError",
"ReasoningEffort",
"ToolInheritancePolicy",
"UsageLimitExceeded",
"UsageLimits",
"context_schema",
"codemode",
"context_codemode_result_adapter",
"create_agent",
"create_codemode_tool",
"format_context",
"make_context",
"register_context",
"raw_codemode_result_adapter",
"tool",
"published_agent_to_tools",
"preload_published_agent_to_tools",
Expand Down
8 changes: 6 additions & 2 deletions hyperforge/src/hyperforge/harness_sdk/harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
HarnessToolCall,
)
from .storage import HarnessStorageProtocol, InMemoryHarnessStorage
from .tools import HarnessTool, ToolCallContext
from .tools import HarnessTool, ToolCallContext, ToolInheritancePolicy
from .tools.core import DictOutput, SendMessageInput, SpawnAgentInput, create_core_tools
from .usage import HarnessUsage, UsageLimitExceeded, UsageLimits

Expand Down Expand Up @@ -1036,7 +1036,11 @@ def _create_child(self, child_id: str, *, include_history: bool) -> AgentHarness
model=self._config.model,
model_client=self._config.model_client,
reasoning_effort=self._config.reasoning_effort,
tools=self._config.tools,
tools=(
tool
for tool in self._config.tools
if tool.inheritance == ToolInheritancePolicy.INHERIT
),
system_prompt=self._config.system_prompt,
title=self._config.title,
conversation_id=self.conversation_id,
Expand Down
10 changes: 9 additions & 1 deletion hyperforge/src/hyperforge/harness_sdk/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
HarnessTool,
ToolCallContext,
ToolHandler,
ToolInheritancePolicy,
tool,
)

__all__ = ["ContextFactory", "HarnessTool", "ToolCallContext", "ToolHandler", "tool"]
__all__ = [
"ContextFactory",
"HarnessTool",
"ToolCallContext",
"ToolHandler",
"ToolInheritancePolicy",
"tool",
]
Loading
Loading