Tools and connected services
Turn typed Python functions into tools, or discover them from an MCP server. Local functions and remote services use the same validation and permission checks.
An agent harness with durable execution, typed decisions, reusable extensions, multi-agent orchestration, OKF support, and NVIDIA OpenShell sandboxes
Build your first agentOpen source.MIT licensed.
View on GitHubExplore models, tools, memory, permissions, and more using the concept buttons below.
Visit DataGOLResearch a topic across web search and your own MCP server, set what each tool is allowed to do, fail over to a second model when the first one stumbles, and save a sourced brief.
HarnessX builds the tool from its type hints and docstring. Its permission is one optional argument: ALLOW, ASK, or DENY.
If Claude fails temporarily, the same step runs on your OpenAI model. Each step gets three attempts, and a rate limit gets more. Fallbacks are configuration, so the agent builds and closes the chain.
The server's tools join the agent, and permission="allow" sets their permission. async with closes the connection when the run ends.
Pass in the tools, the MCP server, and the configuration.
run_stream prints the brief as it is written. raise_for_status() stops a failed run, so research.md is only written for a complete brief.
import asyncio
import os
from pathlib import Path
from ddgs import DDGS
import harnessx as harness
tools = harness.ToolRegistry()
# permission is optional; tools are allowed by default. ASK pauses for an
# approval callback, and DENY blocks the tool.
@tools.register(permission=harness.PermissionLevel.ALLOW)
def search_web(query: str) -> list[dict]:
"""Search the web for titles, URLs, and short excerpts."""
return DDGS(timeout=10).text(query, max_results=5)
# Change this to whatever you want researched.
TOPIC = "running Python agents in a sandboxed container"
TASK = (
f"Research {TOPIC} using the search and MCP tools available to you. "
"Write a Markdown brief covering the steps to adopt it, where the limits "
"are, and what you could not verify. End with links to the sources you "
"actually used."
)
async def main():
# Use Claude, and switch to an OpenAI model if Claude fails temporarily.
config = harness.AgentConfig(
provider="anthropic",
fallbacks=[harness.Fallback("openai", model=os.environ["FALLBACK_MODEL"])],
retry=harness.RetryPolicy(attempts=3),
)
async with harness.MCPManager() as mcp:
research_server = harness.MCPServerConfig.http(
"research",
"https://mcp.example.com/mcp", # Your MCP server
permission="allow", # Allow this server's tools
)
await mcp.connect(research_server)
async with harness.Agent(
tools=tools,
mcp=mcp,
config=config,
) as agent:
# Print the brief as it is written.
async with agent.run_stream(TASK) as stream:
async for event in stream:
if event.type == harness.RunEventType.TEXT_DELTA:
print(event.data, end="", flush=True)
result = await stream.result()
result.raise_for_status()
Path("research.md").write_text(result.output, encoding="utf-8")
if __name__ == "__main__":
asyncio.run(main())
Turn typed Python functions into tools, or discover them from an MCP server. Local functions and remote services use the same validation and permission checks.
Search Open Knowledge Format bundles with BM25F. Read matching concepts with their provenance, then follow links to related facts when the answer needs more context.
Load task-specific instructions when they are needed. Delegate focused work to agents with their own tools and context, then bring their results back to the parent.
Package tools, hooks, and state into extensions. Use the companion Jev SDK for typed routing, classification, and answer review within your workflow.
Before an action
Allow searches, ask before a write, or deny a tool entirely. Set iteration limits and configure a cost budget for the run.
Permissions and budgetsDuring model calls
Set retry limits and capped waits. Add a fallback model or provider for transient failures, with a model ID and reply budget for each member of the chain.
Retries and fallback modelsAcross interruptions
Use SQLite or PostgreSQL to persist run state, tool results, and approval waits. Resume the session after your application restarts.
Durable runsAfter a run
Capture model and tool boundaries, export a portable .hx incident, and inspect its timeline offline without making new model calls.
Flight recorderA function becomes a tool, the model calls it, and the loop keeps going until the work is done.