Skip to main content

A minimal Python SDK for agent loops, with streaming events, clean tool contracts, and zero ceremony.

Project description

EasyHarness

A minimal Python SDK for agent loops, with streaming events, clean tool contracts, and zero ceremony.

Python 3.10+ FileGlide Strands Agents LiteLLM

Small public surface. Strong defaults. Explicit control.

Why EasyHarness

EasyHarness is designed for local agent workflows where a tiny SDK surface matters more than a large framework.

It works well for coding agents, but it is not limited to them. Any agent that benefits from strict tool contracts, streaming events, and explicit runtime control can use the same SDK surface.

It gives you:

  • A single primary runtime entry point through Agent
  • Strict tool metadata and output contracts through tool and ToolOutput
  • A unified streaming event model for thinking, tool, assistant, compression, and system events
  • An official FileGlide-backed filesystem toolset that can be auto-loaded by default or scoped explicitly

Public Surface

The root package intentionally exposes only five public names:

  • Agent
  • ModelConfig
  • AgentEvent
  • ToolOutput
  • tool

Everything else stays behind internal modules or the explicit easyharness.toolset package.

Installation

EasyHarness supports both pip and uv.

Install with pip

pip install -U easyharness

Install with uv

uv add -U easyharness

Verify the installation

After installation, import the public package surface in Python:

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

If the import succeeds, the SDK is ready to use.

Typical Usage

1. Define a tool and run an agent

from easyharness import Agent, ModelConfig, ToolOutput, tool


@tool(
    name="ping_tool",
    purpose="Return a fixed response for a minimal tool-flow check.",
    when_to_use=(
        "Use this when the model needs a trivial tool call to verify that the "
        "tool pipeline is available."
    ),
    parameters={},
    returns="A fixed pong response.",
    common_failures=["This tool does not fail under normal conditions."],
)
def ping_tool() -> ToolOutput:
    return ToolOutput(
        data={"value": "pong"},
        model_text="pong",
        preview="pong",
        detail='{"value": "pong"}',
    )


agent = Agent(
    model=ModelConfig(
        model="openai/gpt-4.1-mini",
        api_key="YOUR_API_KEY",
    ),
    system_prompt="You are a precise agent.",
    tools=[ping_tool],
)

print(agent.run("Call the ping tool and confirm that the tool pipeline works."))

2. Use the default FileGlide integration

Agent can auto-load the official FileGlide-backed toolset, so the shortest agent setup does not need a manual tools=[...] list.

from easyharness import Agent, ModelConfig


agent = Agent(
    model=ModelConfig(
        model="openai/gpt-4.1-mini",
        api_key="YOUR_API_KEY",
    ),
    system_prompt="You are a careful agent.",
)

print(
    agent.run(
        "List the workspace, read pyproject.toml, and summarize the SDK shape."
    )
)
Official default FileGlide tools
  • fileglide_list_tree
  • fileglide_search_paths
  • fileglide_read_text
  • fileglide_search_text
  • fileglide_edit_text
  • fileglide_manage_paths
  • fileglide_inspect_path

3. Disable the default FileGlide tools

If you want a stricter default runtime with no filesystem tool auto-loading, disable it explicitly.

from easyharness import Agent, ModelConfig


agent = Agent(
    model=ModelConfig(
        model="openai/gpt-4.1-mini",
        api_key="YOUR_API_KEY",
    ),
    system_prompt="You are a careful agent.",
    enable_fileglide=False,
)

4. Build a scoped official FileGlide toolset

When you need explicit control over the filesystem scope, disable the default auto-load and pass a scoped official toolset from easyharness.toolset.

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


agent = Agent(
    model=ModelConfig(
        model="openai/gpt-4.1-mini",
        api_key="YOUR_API_KEY",
    ),
    system_prompt="You are a careful agent.",
    enable_fileglide=False,
    tools=build_fileglide_tools(default_root="D:/Projects/PythonProjects/EasyHarness"),
)

build_fileglide_tools(default_root=...) creates the official scoped toolset. Paths that escape the configured root are rejected by FileGlide scope protection.

Each official fileglide_* tool also accepts an optional root argument for that single call. When provided, it overrides the builder's default root without additional SDK-level path-range restrictions.

tools = build_fileglide_tools(default_root="D:/Projects/PythonProjects/EasyHarness")
tool_map = {tool.tool_name: tool for tool in tools}

result = tool_map["fileglide_read_text"](
    target="EasyHarness/pyproject.toml",
    root="D:/Projects/PythonProjects",
)

Model Configuration

ModelConfig keeps the public runtime inputs direct and small. Most callers only need model and api_key.

context_window_limit is available as an optional override when you already know the real model capacity:

from easyharness import ModelConfig


config = ModelConfig(
    model="openai/gpt-4.1-mini",
    api_key="YOUR_API_KEY",
    context_window_limit=131072,
)

When context_window_limit is omitted, EasyHarness resolves it in this order:

  • explicit caller value
  • known SDK model metadata
  • fallback value 200000

Provider-prefixed model IDs such as openai/gpt-4.1-mini are normalized during metadata lookup, so proactive compression does not depend on downstream warning fallbacks.

Conversation Compression

EasyHarness uses an eventing summarizing conversation manager by default. That default policy is owned by the SDK rather than inherited implicitly from the current Strands version.

The default compression behavior is:

  • proactive compression enabled by default
  • proactive compression threshold at 70% of the model context window
  • summary_ratio=0.3
  • preserve_recent_messages=8

This means agent.stream(...) may emit compress events before a model call actually overflows its context window.

If you need a different policy, pass a custom conversation_manager. If you want to keep the built-in eventing manager but change its defaults, create it explicitly and pass it to Agent:

from easyharness import Agent, ModelConfig
from easyharness._internal.conversation import (
    EventingSummarizingConversationManager,
)


agent = Agent(
    model=ModelConfig(
        model="openai/gpt-4.1-mini",
        api_key="YOUR_API_KEY",
    ),
    system_prompt="You are a careful agent.",
    conversation_manager=EventingSummarizingConversationManager(
        proactive_compression=None,
        preserve_recent_messages=12,
    ),
)

Cancellation

EasyHarness exposes explicit cooperative cancellation through Agent.cancel().

Use it when the current invocation is still running and the caller wants to stop it without reaching into internal runtime objects:

import threading
import time

from easyharness import Agent, ModelConfig


agent = Agent(
    model=ModelConfig(
        model="openai/gpt-4.1-mini",
        api_key="YOUR_API_KEY",
    ),
    system_prompt="You are a careful agent.",
)


def consume() -> None:
    for event in agent.stream("Start a long-running task."):
        print(event.kind, event.status, event.text)


worker = threading.Thread(target=consume)
worker.start()
time.sleep(1)
agent.cancel()
worker.join()

Calling agent.cancel() while idle is a no-op. After cancellation, the same Agent instance remains reusable for the next run(...) or stream(...) call.

Event Stream

agent.stream(prompt) yields a unified AgentEvent stream. The public event kinds are:

  • thinking
  • tool
  • assistant
  • compress
  • system

Each event uses the same status vocabulary:

  • started
  • delta
  • completed
  • failed
  • cancelled
for event in agent.stream("Inspect the workspace and explain the next step."):
    print(event.kind, event.status, event.text)

compress events cover both proactive and reactive compression attempts. The event data payload includes the compression mode so UIs can distinguish between "compressed early to stay under the limit" and "compressed during overflow recovery".

When an invocation is cancelled, the stream exposes cancellation as a first- class public outcome:

  • the currently active public phase emits a terminal cancelled event when a phase was already in progress
  • the stream always ends with AgentEvent(kind="system", status="cancelled", data={"stop_reason": "cancelled"})

run(prompt) remains the shortest synchronous path for plain text usage, but stream(prompt) is the authoritative interface for UIs that need complete runtime state, phase transitions, and cancellation semantics.

Design Boundaries

EasyHarness is intentionally small. v1 does not try to solve:

  • UI components
  • Environment-variable orchestration layers
  • Plugin platforms
  • Multi-agent orchestration
  • Root-package re-export sprawl for toolset builders

Summary

EasyHarness is most useful when you want:

  • A minimal Agent API
  • Strict tool definitions
  • Streaming runtime visibility
  • A practical, official FileGlide-backed filesystem toolset

If that is your shape of problem, the shortest path is:

  1. Create an Agent
  2. Define tools with @tool(...)
  3. Use the default FileGlide integration or pass a scoped official toolset

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

easyharness-0.1.3.tar.gz (36.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

easyharness-0.1.3-py3-none-any.whl (28.0 kB view details)

Uploaded Python 3

File details

Details for the file easyharness-0.1.3.tar.gz.

File metadata

  • Download URL: easyharness-0.1.3.tar.gz
  • Upload date:
  • Size: 36.7 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for easyharness-0.1.3.tar.gz
Algorithm Hash digest
SHA256 c74c2c93117506a787bc59648e23d8a84194bd2e3df6e9eaaa0e2f5056c9e30f
MD5 f503edcfa1e4a10e7b5fe9e36c565ea7
BLAKE2b-256 999d9ed37dc9cae369a551fd688608c1e7753e79ece3645a3126b52969de7584

See more details on using hashes here.

File details

Details for the file easyharness-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: easyharness-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 28.0 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for easyharness-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 0025b8cb84c4397e35fc2f76301e03b437defad0b6f1b02d5f8a45b4ac945ec3
MD5 ecf13279c5875c5b515f7ba9b0f0d690
BLAKE2b-256 3cf4a9f876eeaa876b1b71a25c9f5ec4e927e6947323b33d14ab0a753615c259

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page