Dynamically compose the right agent harness for any task from pre-vetted, reusable components.
Project description
Harness Composer
The missing layer between your agent and disaster.
Dynamically compose the right guardrails, tools, context strategy, and verification checks for any task — automatically, from parts that have already been built and trusted.
The Problem Nobody Has Solved Yet
At AI Engineer Europe, Tejas Kumar (IBM) put it plainly: "A model's reliability on agentic tasks comes almost entirely from the harness wrapped around it, not the model itself."
Then he named the open problem: dynamically assembled, just-in-time harnesses.
Today, every agent harness is hand-built in advance by an engineer who has to anticipate every tool, every guardrail, every verification step — before the agent ever sees a real task.
The result: a flight-booking agent and a document-summarisation agent get the same static harness. Or none at all.
Today With Harness Composer
───────────────────────────────── ─────────────────────────────────────────────
Engineer writes harness in advance System looks at the task, assembles the right
for every agent, for every use harness from pre-approved parts — automatically
case. Works until it doesn't. every single time.
How It Works
Harness Composer looks at the task in front of it and assembles the right harness for that task, from components that have already been built, reviewed, and approved.
The novelty is in the assembly, not the components.
This is the SonarQube model: SonarQube doesn't invent a new static analysis rule when it sees your codebase. It detects what your codebase is and applies the pre-built rule set that fits. That's what makes it governable.
┌──────────────────────┐
Incoming Task ───▶│ Task Classifier │
│ · action type │
│ · reversibility │
│ · data sensitivity │
│ · external systems │
└──────────┬───────────┘
│ TaskProfile
▼
┌──────────────────────┐
│ Component Library │
│ ┌────────────────┐ │
│ │ Tool Wrappers │ │ pre-built
│ │ Ctx Strategies │ │ versioned
│ │ Guardrail Sets │ │ human-reviewed
│ │ Verif. Checks │ │ independently tested
│ └────────────────┘ │
└──────────┬───────────┘
│ selects relevant components
▼
┌──────────────────────┐
│ Composition Engine │──▶ HarnessConfig
│ (config, not code) │ (serialisable, auditable)
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Framework Adapter │
│ LangChain · Bedrock │
│ ADK · Azure │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Agent runs with the │
│ right harness │
└──────────────────────┘
See It In Action
Two tasks. Same system. Dramatically different harnesses.
from harness_composer import HarnessComposer
from harness_composer.registry import default_registry
composer = HarnessComposer(registry=default_registry())
Task 1: High-stakes booking
harness = composer.compose(
"Book me a flight to Edinburgh next Tuesday and pay with my corporate card."
)
print(harness.summary())
HarnessConfig(
risk=critical, action=transact,
context=context_compression,
guardrails=[financial_threshold, irreversibility_confirmation],
verification=[external_confirmation]
)
Task 2: Read-only summary
harness = composer.compose(
"Summarise this internal document for the quarterly review."
)
print(harness.summary())
HarnessConfig(
risk=low, action=read,
context=context_minimal,
guardrails=[],
verification=[]
)
Same API call. The right harness, automatically. No engineer had to write either of them in advance.
Quickstart
pip install -e ".[dev]"
# See both examples side by side
python examples/flight_booking.py
python examples/document_summary.py
Wire into a LangChain agent
from harness_composer import HarnessComposer
from harness_composer.registry import default_registry
from harness_composer.adapters.langchain import LangChainAdapter
registry = default_registry()
composer = HarnessComposer(registry=registry)
adapter = LangChainAdapter(registry=registry)
task = "Transfer £800 to the supplier's account."
harness = composer.compose(task)
# Option A — inject into an AgentExecutor
agent = adapter.inject(harness, agent_executor)
# Option B — pass as RunnableConfig (non-mutating)
config = adapter.build_runnable_config(harness)
chain.invoke({"input": task}, config=config)
The adapter:
- Runs guardrail checks before every tool call (
on_tool_start) - Runs verification checks after every tool call (
on_tool_end) - Filters the agent's tool list to only what the harness authorises (least privilege)
- Raises
GuardrailViolationonBLOCKoutcomes — the agent sees a hard error, not a silent bypass
The Component Library
Every component is pre-built, versioned, and human-reviewed before it enters the registry. Nothing reaches an agent through dynamic discovery.
Tool Wrappers
| Component | What it does |
|---|---|
web_search |
Read-only web search. Inject any real client (Tavily, SerpAPI). |
http_request |
Generic HTTP with domain allow-listing and mutation gating. |
Context Strategies
| Component | When to use |
|---|---|
context_minimal |
Short, single-step tasks. Keeps system prompt + last N messages only. |
context_compression |
Multi-step tasks. Running summary of old messages + tail verbatim. |
Guardrail Sets
| Component | What it catches |
|---|---|
financial_threshold |
Blocks/escalates transactions above configurable soft and hard limits. |
pii_redaction |
Detects and redacts emails, phone numbers, NI/SSN, postcodes, card numbers. |
irreversibility_confirmation |
Requires explicit confirmation token before irreversible actions. |
Verification Checks
| Component | What it confirms |
|---|---|
external_confirmation |
Re-queries the external system directly — not the agent's self-report. |
database_write |
Re-reads the record after a write to confirm it actually persisted. |
Why "Compose, Don't Generate"
The scary version of this idea is an agent that writes its own guardrails at runtime. Nobody should want that.
The safe version — the one worth building — is narrower: the system selects from a library of components that were built once, reviewed once, tested once, and approved once. The composition step generates a configuration, not code.
This is what keeps it governable.
The composition engine never writes new logic. It assembles existing, trusted parts into the right combination for the task at hand.
Every harness decision is auditable because the classifier records exactly which signals fired and why — stored in TaskProfile.matched_signals. You can reconstruct any composition decision from logs alone.
The Classifier Is Explainable By Design
The rules-based classifier records every signal that drove the classification:
profile = composer.classifier.classify(
"Book me a flight to Edinburgh and pay with my corporate card."
)
print(profile.matched_signals)
# ['payment', 'book_ticket', 'flight_api', 'payment_api']
print(profile.risk_level) # RiskLevel.CRITICAL
print(profile.action_type) # ActionType.TRANSACT
print(profile.is_reversible) # False
print(profile.requires_human_approval) # True
print(profile.classifier_version) # 'rules-based-1.0'
An auditor can trace exactly why a given task got its harness. No black box.
Governance: Adding a Component
The entire value proposition rests on every component being trustworthy. The governance gate is the PR — not the runtime.
-
Create your component in the appropriate
library/sub-package, subclassing the relevant base:class PaymentProcessorWrapper(BaseToolWrapper): ...
-
Tag it accurately — tags drive composition selection:
tags=frozenset({"financial", "payment", "payment_processor"})
-
Write tests in
tests/test_components.py— every component must be independently testable. -
Register it in
default_registry()inregistry.py:PaymentProcessorWrapper(api_key=..., allowed_domains={"api.stripe.com"}),
-
Open a PR. The review happens here — not at agent runtime.
The registry is intentionally append-only at runtime. A component that is registered cannot be replaced or removed without a restart and a code change.
Roadmap
- Rules-based task classifier with explainable signal recording
- Component library: 2 tool wrappers, 2 context strategies, 3 guardrails, 2 verification checks
- Composition engine: tag-based, fully deterministic
- LangChain / LangGraph adapter
- AWS Bedrock AgentCore adapter (HookProvider lifecycle events)
- Google ADK adapter (plugin system)
- LLM-based classifier (once there is enough production signal data to validate it against rules-based)
- Mid-task escalation — harness can escalate based on what the agent discovers during execution, not just at task start
- Agent track record integration — lighter harnesses for agents with strong proven histories
What This Is Not
- Not a prompt framework. Harness Composer doesn't touch your prompts.
- Not a model router. It doesn't choose which model runs the task.
- Not an agent framework. It wraps around whichever framework you already use.
- Not a generative guardrail system. It never writes new safety logic at runtime.
Running the Tests
python3.11 -m pytest tests/ -v
70 passed in 1.19s
Origin
This project was built from a product gist that came out of a conversation around Tejas Kumar's (IBM) talk at AI Engineer Europe, where he identified dynamically assembled, just-in-time harnesses as the open problem in agent reliability.
The full product thinking is in product_gist_harness_composer.md.
Contributing
Pull requests are welcome. For significant changes, please open an issue first to discuss what you'd like to change.
For component contributions specifically: follow the governance steps above. Every component that reaches the registry is a commitment — to users of this library and to their agents' downstream actions.
See CONTRIBUTING.md for details.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file harness_composer-0.1.0.tar.gz.
File metadata
- Download URL: harness_composer-0.1.0.tar.gz
- Upload date:
- Size: 37.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6bbf59a6896a7666eb297be9f3b8d110f549c15b158bbd71cae4d2762f4ae325
|
|
| MD5 |
8164e9712c041e351ddb98e347423a01
|
|
| BLAKE2b-256 |
3395696841fb18467d6f8225c176cf9166cb6537c8a8332877ed430e2e1736c0
|
Provenance
The following attestation bundles were made for harness_composer-0.1.0.tar.gz:
Publisher:
publish.yml on routsom/harness-composer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
harness_composer-0.1.0.tar.gz -
Subject digest:
6bbf59a6896a7666eb297be9f3b8d110f549c15b158bbd71cae4d2762f4ae325 - Sigstore transparency entry: 1881154921
- Sigstore integration time:
-
Permalink:
routsom/harness-composer@0898bb07c05cf44c5b007cf7a6fb4669cbf54f25 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/routsom
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0898bb07c05cf44c5b007cf7a6fb4669cbf54f25 -
Trigger Event:
release
-
Statement type:
File details
Details for the file harness_composer-0.1.0-py3-none-any.whl.
File metadata
- Download URL: harness_composer-0.1.0-py3-none-any.whl
- Upload date:
- Size: 48.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db59a29ae48b211e9d8284294aa3a2ae3596eec982610efc1e71e02bfd63309a
|
|
| MD5 |
09ce70094a2f6c9a02b2f231e4671ff7
|
|
| BLAKE2b-256 |
936f7e639d8cfd8899de5d906d83eadfebd5c0fafd517e3ee138270c7ec7abf6
|
Provenance
The following attestation bundles were made for harness_composer-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on routsom/harness-composer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
harness_composer-0.1.0-py3-none-any.whl -
Subject digest:
db59a29ae48b211e9d8284294aa3a2ae3596eec982610efc1e71e02bfd63309a - Sigstore transparency entry: 1881154990
- Sigstore integration time:
-
Permalink:
routsom/harness-composer@0898bb07c05cf44c5b007cf7a6fb4669cbf54f25 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/routsom
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0898bb07c05cf44c5b007cf7a6fb4669cbf54f25 -
Trigger Event:
release
-
Statement type: