Skip to main content

EasyHarness

Fast agents. Full control.

A compact Python SDK for strict tool contracts, observable streaming events, and scoped local file capabilities.

PyPI Python License GitHub stars

FileGlide Strands Agents LiteLLM

One Agent. Strict tools. Every phase visible.

Why · Quick start · Capabilities · Usage · API · Boundaries


Why EasyHarness

Most agent applications become difficult when tool behavior is vague, the UI cannot see real runtime phases, filesystem scope is unclear, and context failure arrives after the fact.

EasyHarness keeps those concerns in a small Python SDK. It is built for coding agents, but fits any single-agent workflow that needs reliable tool calls and observable execution.

Where agents drift What EasyHarness makes explicit
A tool is only a function, so the model does not know when to use it or how it fails. @tool requires purpose, invocation guidance, parameter descriptions, return semantics, and common failures.
A product can only guess what an agent is doing. stream() emits unified thinking, tool, assistant, compression, and system events.
Filesystem access is enabled without a legible boundary. The official FileGlide toolset supports a default root and an explicit root per call.
Long sessions fail only after the model context overflows. The default manager proactively compresses at 70% of the context window and reports the result as an event.

Quick Start

Run this from the project directory you want the agent to inspect. When no tools are supplied, Agent loads the official FileGlide toolset scoped to the current working directory.

pip install -U easyharness
import os

from easyharness import Agent, ModelConfig


agent = Agent(
    model=ModelConfig(
        model="gpt-5.4",
        api_key=os.environ["OPENAI_API_KEY"],
    ),
    system_prompt="You are a careful code reviewer. Read files before answering.",
)

print(agent.run("Read README.md and pyproject.toml. List three core capabilities."))

This is the complete first loop: create a session-oriented agent, load filesystem tools, inspect the local project, and return text. The caller supplies the API key explicitly; EasyHarness does not read or orchestrate environment variables.

[!TIP] With uv, run uv add -U easyharness. Configure the model ID, base_url, and context window through ModelConfig.

Core Capabilities

Capability What you get
One runtime entry point Use Agent.run() for final text or Agent.stream() for a live experience.
Strict tool contracts Tool metadata, function signatures, and parameter documentation must agree. ToolOutput can serve both the model and a UI.
Host context injection Pass runtime-only data with ToolContext[T] or OptionalToolContext[T]; it stays out of the model schema and receives deep type validation.
Observable events One event vocabulary: thinking, tool, assistant, compress, and system.
Explicit session control cancel() cooperatively stops the active call, reset() clears session history, and re-entry raises AgentBusyError.
Scoped file operations Seven FileGlide tools cover listing, search, reading, editing, path management, and inspection.
Proactive compression The default manager compresses at 70% of the context window, keeps eight recent messages, and emits its outcome.
OpenAI-compatible models Supply a model ID, API key, base_url, sampling parameters, and a context-window override. A DeepSeek-compatible path preserves tool-call reasoning.

Common Patterns

Define a strict tool

@tool does more than register a Python function. It produces a model-facing contract and rejects incomplete metadata before runtime.

from easyharness import Agent, ModelConfig, ToolOutput, tool


@tool(
    name="get_build_status",
    purpose="Read the latest build status.",
    when_to_use="Use when the user asks whether the latest build passed.",
    parameters={},
    returns="A normalized build-status result.",
    common_failures=["No build record is available."],
)
def get_build_status() -> ToolOutput:
    return ToolOutput(
        data={"status": "passed"},
        model_text="The latest build passed.",
        preview="Build passed",
        detail='{"status": "passed"}',
    )


agent = Agent(
    model=ModelConfig(model="gpt-5.4", api_key="YOUR_API_KEY"),
    system_prompt="You are a release assistant.",
    tools=[get_build_status],
    enable_fileglide=False,
)

print(agent.run("Did the latest build pass?"))

Drive a live interface

stream() is the authoritative interface for a timeline, progress UI, or cancel control. Every event carries kind, status, timing information, and optional data.

for event in agent.stream("Inspect the project and explain the next step."):
    print(event.kind, event.status, event.name, event.text)

The shared statuses are started, delta, completed, failed, and cancelled. On cancellation, the active phase ends as cancelled, the stream ends with system/cancelled, and the same Agent remains reusable.

agent.cancel()  # A no-op while idle; requests cooperative cancellation while running.
agent.reset()   # Clears the current session history.

Keep host data out of the model

Some values belong to your application and tool implementation, not to model-visible tool input: tenant identity, permission state, or a request object. Mark those arguments as ToolContext[T] or OptionalToolContext[T]; EasyHarness hides them from the schema and injects validated values on each run() or stream() call.

from dataclasses import dataclass

from easyharness import Agent, ModelConfig, ToolContext, ToolOutput, tool


@dataclass(frozen=True)
class RequestContext:
    tenant_id: str


@tool(
    name="tenant_summary",
    purpose="Read a summary for the active tenant.",
    when_to_use="Use when the user asks about the active tenant.",
    parameters={},
    returns="A summary for the active tenant.",
    common_failures=["The request context was not supplied."],
)
def tenant_summary(request: ToolContext[RequestContext]) -> ToolOutput:
    return ToolOutput(model_text=f"Active tenant: {request.tenant_id}")


agent = Agent(
    model=ModelConfig(model="gpt-5.4", api_key="YOUR_API_KEY"),
    system_prompt="Call tenant_summary when the user asks about the active tenant.",
    tools=[tenant_summary],
    enable_fileglide=False,
)

print(
    agent.run(
        "What is my active tenant?",
        request=RequestContext(tenant_id="acme"),
    )
)

Scope filesystem access

Build a scoped toolset when an agent should operate inside one project. Relative and absolute paths must remain within that root. To use another root for one call, pass an explicit root; .. cannot escape the scope.

from easyharness import Agent, ModelConfig
from easyharness.toolset import build_fileglide_tools


agent = Agent(
    model=ModelConfig(model="gpt-5.4", api_key="YOUR_API_KEY"),
    system_prompt="You are a careful local code assistant.",
    enable_fileglide=False,
    tools=build_fileglide_tools(default_root="D:/Projects/my-app"),
)

The official tools are fileglide_list_tree, fileglide_search_paths, fileglide_read_text, fileglide_search_text, fileglide_edit_text, fileglide_manage_paths, and fileglide_inspect_path.

Tune model and context behavior

ModelConfig requires only model and api_key. Add base_url, temperature, top_p, seed, or context_window_limit as needed. When no context limit is provided, the SDK tries known model metadata before falling back to 200000.

The default conversation manager uses summary_ratio=0.3, preserve_recent_messages=8, and a 70% proactive-compression threshold. Pass a custom conversation_manager to change that policy; compression start, completion, and failure appear as compress events.

Public API

The root package deliberately exposes only eight names:

from easyharness import (
    Agent,
    AgentBusyError,
    AgentEvent,
    ModelConfig,
    OptionalToolContext,
    ToolContext,
    ToolOutput,
    tool,
)

The file-tool builder lives in an explicit subpackage:

from easyharness.toolset import build_fileglide_tools

Design Boundaries

EasyHarness owns the single-agent runtime loop and its tool, session, and event contracts. It intentionally does not provide:

  • UI components;
  • environment-variable orchestration;
  • a plugin platform;
  • multi-agent orchestration; or
  • broad root-package re-exports for every toolset builder.

That boundary keeps the SDK responsible for predictable runtime behavior while leaving product orchestration and experience design to the caller.

Development

Sync the development environment, then run the SDK regression suite that does not require real model credentials:

uv sync
uv run python -m unittest tests.test_sdk tests.test_context_window_resolution

Contributing

Contributions should improve verifiable runtime behavior, tool contracts, event completeness, filesystem scope, or session control. Keep the public API narrow and add independently runnable tests for new behavior.

License

MIT

Metadata

Release files for easyharness 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for easyharness 0.2.0
File Size Uploaded
easyharness-0.2.0.tar.gz 45.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for easyharness 0.2.0
File Interpreter ABI Platform
easyharness-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 78.1 kB

Release files / easyharness-0.2.0.tar.gz

Download URL easyharness-0.2.0.tar.gz
Size 45.9 kB
Tags Source
SHA-256 checksum
How to use checksums
7d547cca90ddd35d8227a920e1ab2caf97cc45edd11de45e579ced7aa84949ab
BLAKE2b-256 checksum
How to use checksums
350fe30ed5cdd2700253e2e34001af140ab957e63f2dbe304a3dc4fed3606abd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / easyharness-0.2.0-py3-none-any.whl

Download URL easyharness-0.2.0-py3-none-any.whl
Size 32.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3c1427b06dbe7d44340ec51be05e4a329cb85288668c8324054999d2c2cb972b
BLAKE2b-256 checksum
How to use checksums
dfbb40e04b1a4d917ebe80161b96bffcb1145e68cc15bd9f682c55ee2750af98
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.3.1

2 release files

0.3.0

2 release files

0.2.1

2 release files

This release

0.2.0 This release

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page