Open source.MIT licensed.

View on GitHub
Visit DataGOL

Built for production.

Research 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.

  1. HarnessX builds the tool from its type hints and docstring. Its permission is one optional argument: ALLOW, ASK, or DENY.

  2. 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.

  3. The server's tools join the agent, and permission="allow" sets their permission. async with closes the connection when the run ends.

  4. Pass in the tools, the MCP server, and the configuration.

  5. 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.

A production-grade agent with model fallback and tool permissions configured
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())

Give your agent more to work with.

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.

Memory across conversations

Keep the current conversation in context, expose saved notes and structured facts through tools, and save session snapshots when work needs to continue later.

Knowledge with sources

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.

Skills and specialist agents

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.

Progress your app can use

Stream text and tool events into your interface. Add hooks to observe execution, or middleware to transform requests and results. Connect tracing when you need deeper diagnostics.

Reusable behavior and decisions

Package tools, hooks, and state into extensions. Use the companion Jev SDK for typed routing, classification, and answer review within your workflow.

Keep control of the work.

Before an action

Decide what needs approval.

Allow searches, ask before a write, or deny a tool entirely. Set iteration limits and configure a cost budget for the run.

Permissions and budgets

During model calls

Handle temporary model failures.

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 models

Across interruptions

Continue unfinished work.

Use SQLite or PostgreSQL to persist run state, tool results, and approval waits. Resume the session after your application restarts.

Durable runs

After a run

See what actually happened.

Capture model and tool boundaries, export a portable .hx incident, and inspect its timeline offline without making new model calls.

Flight recorder

Your first agent runs in a few minutes.

A function becomes a tool, the model calls it, and the loop keeps going until the work is done.

Open the quickstart