{"id":50541670,"url":"https://github.com/hanlinlibham/able-harness-kit","last_synced_at":"2026-06-03T20:30:38.497Z","repository":{"id":361646377,"uuid":"1255142084","full_name":"hanlinlibham/able-harness-kit","owner":"hanlinlibham","description":"Thin, backend-neutral agent-harness middlewares for LangChain / deepagents: loop guard, binary-read guard, tool-result budget.","archived":false,"fork":false,"pushed_at":"2026-05-31T17:05:07.000Z","size":14,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-31T17:23:21.573Z","etag":null,"topics":["agent","agent-harness","agent-observability","deepagents","langchain","llm","middleware","python"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hanlinlibham.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-31T13:10:29.000Z","updated_at":"2026-05-31T17:05:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hanlinlibham/able-harness-kit","commit_stats":null,"previous_names":["hanlinlibham/able-harness-kit"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/hanlinlibham/able-harness-kit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hanlinlibham%2Fable-harness-kit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hanlinlibham%2Fable-harness-kit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hanlinlibham%2Fable-harness-kit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hanlinlibham%2Fable-harness-kit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hanlinlibham","download_url":"https://codeload.github.com/hanlinlibham/able-harness-kit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hanlinlibham%2Fable-harness-kit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33878990,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-03T02:00:06.370Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent","agent-harness","agent-observability","deepagents","langchain","llm","middleware","python"],"created_at":"2026-06-03T20:30:37.718Z","updated_at":"2026-06-03T20:30:38.487Z","avatar_url":"https://github.com/hanlinlibham.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# able-harness-kit\n\n**Thin, backend-neutral agent-harness middlewares for LangChain / deepagents.**\n\nProduction-distilled patterns that compose on top of any LangChain\n`AgentMiddleware` stack (including [deepagents](https://github.com/langchain-ai/deepagents)).\nThey don't replace your agent — they harden the loop around it.\n\n```bash\npip install able-harness-kit          # after the first PyPI release\n# or, from a clone:\npip install -e .\n```\n\n## Why\n\nMost agent failures aren't \"the model isn't smart enough\" — they're the loop\n*around* the model lacking observation and control:\n\n| Middleware | The failure it kills |\n|---|---|\n| `ProgressAwareLoopGuardMiddleware` | The agent repeats a tool call that returns no new information, spinning until it burns its budget — while *not* tripping on polling or pagination, which repeat the call but get new results. |\n| `LoopGuardMiddleware` | The cheaper case: the agent calls the same tool with identical args N times in a row (no view of the result needed). |\n| `BinaryReadGuardMiddleware` | `read_file` hands the model a base64 block for a binary file; a lossy gateway drops it; the model *claims it read the file* and hallucinates. |\n| `ToolResultBudgetMiddleware` | A 200 KB tool dump blows the context window and forces premature summarization. |\n\nEach is **backend-neutral** (depends only on LangChain / LangGraph public\ntypes), small enough to read in one sitting, and composes *with* — rather than\nreplaces — your existing harness.\n\n## Usage\n\n```python\nfrom langchain.agents import create_agent          # or deepagents.create_deep_agent\nfrom able_harness_kit import (\n    ProgressAwareLoopGuardMiddleware,\n    BinaryReadGuardMiddleware,\n    ToolResultBudgetMiddleware,\n)\n\nagent = create_agent(\n    model,\n    tools=[...],\n    middleware=[\n        BinaryReadGuardMiddleware(),                   # fail loud on binary read_file\n        ToolResultBudgetMiddleware(limit=16_000),      # cap oversized tool results\n        ProgressAwareLoopGuardMiddleware(stop_at=3),   # stop loops that make no progress\n    ],\n)\n```\n\n### `ProgressAwareLoopGuardMiddleware`\nThe cheap loop signal — \"same tool, same args, N times\" — is wrong about half the\ntime. Polling a job, walking pagination, or tailing a stream all repeat the call\nand *should*: each one returns new information. The difference between progress and\na stuck loop isn't in the arguments, it's in the **output**.\n\nThis guard runs the tool, compares the result to the previous same-tool result,\nand intervenes only when a call both repeats *and* returns no new information\n(`same_args_repeat_count \u003e= stop_at` **and** `new_information_delta \u003c progress_floor`).\nGenuine progress passes through; a true spin gets a model-facing directive (not a\nsynthetic user message). For tools where a content-hash comparison is too lax — a\nTODO tool re-sent with a one-character cosmetic edit, say — pass\n`delta_overrides={\"write_todos\": make_set_membership_delta(\"todos\")}`.\n\nThe output-delta primitives are exported standalone:\n`build_observations(state[\"messages\"])` reconstructs the per-call observations\n(`ToolCallObservation` with `new_information_delta`) from history with no instance\nstate.\n\n### `LoopGuardMiddleware`\nThe cheaper variant: fingerprints each call (name + normalized args), counts\nconsecutive identical-argument repeats, and short-circuits once `stop_at` is hit —\nwithout looking at the result. Use it when you only want to catch a model hammering\nan identical call and don't need output tracking. The counter resets the moment a\ndifferent call is seen. Pass `on_signal=` to observe without changing control flow.\n\n### `BinaryReadGuardMiddleware`\nIntercepts `read_file` results whose declared media type isn't text-like and\nreplaces the base64 payload with a structured error pointing at a dedicated\nextractor (OCR / doc-to-markdown / type sniff). Turning a silent hallucination\ninto an explicit \"use the right tool\" nudge is strictly safer; the only cost is\none extra tool hop on the first attempt. Tool name, metadata keys, and the\ndirective are configurable; disable per-deployment with\n`BINARY_READ_GUARD_ENABLED=0`.\n\n### `ToolResultBudgetMiddleware`\nCaps each tool result at a character budget. Oversized results are offloaded via\na caller-supplied `offload(content, tool_name) -\u003e OffloadRef` callback (bring\nyour own store — filesystem, blob, vector DB), or truncated with a retrieval\nhint by default.\n\n## Background\n\nThese were distilled from a multi-agent product running on non-Anthropic model\ngateways (Qwen / GLM / DeepSeek), and from a controlled experiment across 5\nmodels × 9 behavioral probes (harness on vs. off) that framed a harness as an\n*entropy-reducing control system* rather than overhead. The progress-aware loop\nsignal — keying off whether a repeated call returned new information, not just\nwhether the arguments repeated — is the open distillation of that controller's\nloop detector. The middlewares here are the reusable, framework-neutral core of\nthat work.\n\n## License\n\nMIT © Hanlin Li\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhanlinlibham%2Fable-harness-kit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhanlinlibham%2Fable-harness-kit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhanlinlibham%2Fable-harness-kit/lists"}