Part 4 of 40 · 8 min read
The harness coordinates the work; it does not become the company
Experimental · Work in progress · Internal R&D
A custom harness for bounded episodes, scoped tools, context assembly and cross-loop handoffs, built above the existing runtime interface.
Arunank Sharan · Kuzushi Labs ·
In this article
I do not want one enormous prompt containing sales, support, finance, hiring and deployment rules. Nor do I want fourteen assistants talking indefinitely to each other. I want a bounded episode: something happened, an agent gets the relevant facts and permitted actions, and the episode ends with a result, a recorded handoff or a request for a decision.
The platform already has the beginnings of this separation. There is an AgentRuntime interface for reasoning adapters and a gateway-oriented harness for routed model calls and action invocation. The next step is to compose them into one consistent execution lifecycle.
The existing reasoning interface is deliberately small
class AgentRuntime(Protocol):
def run(
self,
agent_type: AgentTypeDef,
trigger: Trigger,
) -> Outcome: ...
A trigger carries tenant identity, source, content, interaction reference and metadata. An outcome contains governed decisions and notes. Most current loop adapters use deterministic logic. The Pydantic AI adapter currently exposes a concrete lead-creation tool and uses TestModel by default, which allows local checks without a model API call. That proves a framework can go through the dispatcher; it does not prove a complete tool catalogue is generated from the business contract.
Pydantic AI also supports composable toolsets and dependency-aware tool selection. Those are useful facilities for a future adapter, while the business dispatcher remains the authority boundary. Pydantic AI toolsets.
Four components have different jobs
| Component | Owns | Should not decide |
|---|---|---|
| Orchestrator | Trigger routing, run identity, retries, cancellation and handoffs | Whether a discount is commercially acceptable |
| Context builder | Relevant facts, source versions and retrieval budget | Which facts become authoritative without validation |
| Reasoning adapter | Interpretation, drafting and proposed next actions | Its own permissions |
| Dispatcher | Authorisation, policy, approval and effects | Open-ended business strategy |
The earlier harness routes a request through the model gateway, validates the output, optionally invokes an action and appends a run record to an outbox. Its working path is single-shot. General tool iteration, checkpointed resumption and universal loop dispatch are extension work, not existing behaviour to assume from the class name.
Compose capabilities from the job in front of the agent
For a new enquiry I need lead capture, identity lookup and scope extraction. I do not need refund execution. For a billing dispute I need the agreement, delivery evidence, invoice and support history; I may allow a credit recommendation but not payment execution.
Here is a design sketch for choosing tools:
def available_actions(agent, trigger, business, policy):
candidates = set(agent.calls_actions)
candidates &= set(business.enabled_actions)
candidates &= set(actions_for_event(trigger.event_type))
return tuple(sorted(
name for name in candidates
if policy.may_expose(name, trigger.tenant_id, agent.name)
))
Tool exposure reduces confusion; it does not authorise execution. The dispatcher checks again against fresh state. This matters if the account becomes suspended after context was assembled.
A composed run should also record the selected agent version, prompt version, model route, source object versions and exposed action names. Without that record, a failed run cannot be reproduced reliably after configuration changes.
Bound the episode
For the first live experiment I would start with an explicit ceiling: eight reasoning steps, four external read attempts and one proposed write before a checkpoint. These are proposed operating limits, not measured optimum values. A quote investigation that needs more work should create a resumable task, not silently exhaust a growing context window.
The stop conditions are concrete: completed action with a receipt; missing information; required approval; exhausted budget; cancellation; or unresolvable ambiguity. Tool failure should return a typed result that the agent can interpret, including whether retrying is safe.
Memory is similarly bounded. A long conversation is supporting context. The agreement and invoice records remain the facts on which a write is authorised.
Coordinate loops through handoffs
A closed-won opportunity can trigger delivery preparation. It should not prompt a sales agent to impersonate the delivery agent and mutate its records. The event identifies the business operation and its evidence; the receiving loop chooses its own bounded episode.
I would deduplicate by (tenant, source_event, receiving_workflow, workflow_version) and retain a parent correlation ID across episodes. The founder can then follow one customer commitment without needing to read every token exchange.
The composition work is a continuation of the architecture discussed in the agent-harness series, but this platform is an original internal R&D effort. Its reusable ideas are the execution envelope, scoped context and controlled actions. It does not need a client's domain objects or proprietary code to coordinate the company's own loops.
The remaining engineering question
The hardest part is interrupted work. A process can die while waiting for approval or after a provider accepted an action. A durable workflow engine is one possible implementation for retries and checkpoints; activities still need idempotent effects. Temporal's activity model.
Before widening autonomy, I want a restart test that resumes the same business operation without re-sending the quote. That test tells me more than a long uninterrupted agent demo.
Implement one bounded episode
For a quote task, the first reference runtime has only two useful actions: read scope and propose sending the reviewed quote. The payment tool exists in the catalogue but is outside the sales role. The model returns a structured proposal; the host selects what is exposed and controls when the episode stops.
from company_lab.harness import (
Tool, Budget, ScriptedModel, compose, run_episode,
)
catalogue = [
Tool("read_scope", frozenset({"sales"}), "read", 1),
Tool("send_quote", frozenset({"sales"}), "write", 1),
Tool("submit_payment", frozenset({"finance"}), "write", 1),
]
tools = compose(catalogue, "sales", {t.name for t in catalogue})
model = ScriptedModel([
{"kind": "tool", "tool": "read_scope"},
{"kind": "tool", "tool": "send_quote",
"arguments": {"quote_id": "q17", "version": 3}},
])
result = run_episode(
model, {"tenant": "studio"}, tools,
{"read_scope": lambda args, ctx: {"version": 3, "fee_minor": 600000}},
Budget(remaining_steps=4, remaining_minor=20),
)
assert "submit_payment" not in tools
assert result["state"] == "AWAITING_APPROVAL"
This is runnable local code. ScriptedModel supplies deterministic proposals so the control flow can be tested without API keys. Replacing it with a language-model adapter means validating that provider's structured output and reserving model-call cost before invocation. It does not mean giving the provider a database connection.
A practical run record
I would persist the run's task identity, source event, tenant, agent configuration, model route, tool catalogue hash, input record versions, budget and terminal state. Each step records the proposed tool, validated arguments, result classification, receipt reference and charged cost. Retain a concise decision summary where useful; the business audit should depend on actions and evidence rather than private model reasoning.
The run state needs more than success and failure. WAITING_FOR_INPUT, AWAITING_APPROVAL, BUDGET_EXHAUSTED, CANCELLED and UNKNOWN_EFFECT tell the scheduler different things. Waiting for a customer response should release the worker and create a resumption condition. It should not keep a model call open while the customer sleeps.
Compose at runtime without making permissions dynamic guesses
Suppose a renewal request arrives while a critical issue is open. The orchestrator can compose read access to the agreement, support case and usage evidence. It can expose a renewal-draft tool only if the current policy allows it. A different episode for the same customer may need different tools, but the authority comes from authenticated role and policy, not from a generated agent persona.
I would cache tool schemas by configuration version and still evaluate execution policy against fresh state. Tool descriptions should name side effects, required arguments and failure types. A tool called manage_customer is too broad to review; draft_renewal_offer and send_approved_offer have inspectable boundaries.
The narrow model adapter in the current platform demonstrates this seam with lead creation. The expanded reference runtime generalises the composition contract locally. Durable provider-backed multi-step execution remains the next platform integration.
Recovery is part of the reasoning interface
The agent should know whether a tool failed before execution, was explicitly rejected, or may have completed remotely. Those observations lead to different next actions. For an unknown email send, the correct tool is receipt lookup, not another send with a fresh ID. For stale scope, the correct step is a refreshed read and proposal.
This is where the reasoning-and-acting pattern is useful: new observations can change the next proposed step. ReAct studies that interaction between language-model reasoning and external actions. My adaptation adds a host-owned operation boundary and stop conditions around it. ReAct paper.
The exercise is to replace the scripted proposals with a deliberately bad sequence: request a finance tool, repeat reads until the budget is exhausted, and propose a write without sufficient evidence. The reference tests verify that the host's boundaries remain in control of the episode.