Part 2 of 18 · Arunank Sharan · Kuzushi Labs · 1,340 words · 4 August 2026

← Series contents · Complete reading view

Designing the harness: architecture, Hermes and OpenClaw

The first architecture question is where responsibility lives when the model gets something wrong. If every concern sits inside one agent object, it becomes difficult to answer. Was the wrong tenant selected? Was the correct tool missing? Did a prompt change reach the running worker? Did the backend reject an action that the model described as successful?

The client’s platform separated these questions into components with explicit contracts. The model conversation was one component. The harness around it made the conversation callable, bounded and observable.

The domain backend, Python agent runtime, model gateway and operator console, with a separate configuration and evidence plane.

The four product boundaries

The domain backend, built with Rails and PostgreSQL, owned hospitality records and their rules. The Python agent service, built with FastAPI, owned execution, routing, context assembly, tool dispatch and agent configuration. The Next.js admin console exposed prompts, tools, skills, agents, models, playground sessions, evaluations, observability and spend. A model gateway provided a common operational point for provider access and usage attribution.

The guest-facing web product and messaging integrations were separate entry points into that system. They did not each get their own copy of the agent loop. Their inputs were adapted to a shared event contract, and their outputs were projections of the runtime’s events.

The distinction between the domain database and the agent database matters. Durable agent state does not imply that the agent bypasses the application’s business APIs. Agent runs and configuration belong to the agent service; reservations and task ownership remain with the backend.

Harness, orchestrator and loop are different jobs

The orchestrator prepares and finalises a run. It resolves the session, handles the idempotency gate, obtains routing and context, invokes an attempt and records the outcome.

The harness exposes the execution contract for an attempt. The default implementation delegates to an injected runner. It does not hold a guest’s messages or the current event sink on a shared instance.

The iteration loop performs model turns, assembles streamed tool arguments, executes tools, appends results and decides whether another turn is needed.

The lifecycle envelope wraps the harness with validation, timing, events, deadlines and error classification. Keeping it outside the model loop allows the same failure semantics to apply even when the runner changes.

This separation creates useful replacement seams. A fake transport can exercise routing and tool policy without a model bill. A PostgreSQL store can replace an in-memory store without changing the orchestrator. A synchronous caller and a streaming caller can use the same runtime.

What OpenClaw contributed

The implementation explicitly adapted OpenClaw’s harness boundary and its prepare–send–resolve lifecycle pattern. It also followed an event-oriented approach: execution emits facts that can be projected into different consumer views. OpenClaw’s documented runtime similarly distinguishes session preparation, execution, tools, streaming and lifecycle events. OpenClaw agent loop

The adaptation was selective. This product needed one default harness with replaceable dependencies. It did not need a general selector among many execution backends. Capability methods could remain part of the contract without becoming a framework-sized dispatch layer.

OpenClaw’s gateway also provides a useful reference for keeping channel connectivity and control clients outside the reasoning core. In the client platform, existing backend integrations owned messaging delivery; the agent service accepted normalised requests rather than becoming the owner of every channel connection. OpenClaw gateway architecture

That is an architectural influence, not a claim that the hospitality platform ran on OpenClaw. The runtime was implemented for the client’s service boundaries and domain.

What Hermes contributed—and what changed

Hermes provided a practical reference for a complete agent product: provider resolution, tools, prompt assembly, session state and multiple entry points arranged around the agent conversation. Its architecture is useful precisely because it exposes the surrounding machinery that short agent tutorials omit. Hermes architecture

The local design made two explicit departures. Per-turn callbacks were consolidated into one typed event stream. Session-specific state was passed into execution instead of being kept on the shared harness instance. The service needed to handle unrelated tenants concurrently, so a singleton with mutable conversation fields would be the wrong ownership model.

Hermes’ reusable skills are another useful reference: procedures can be loaded when relevant rather than embedding every procedure in the base prompt. Here, skills became versioned database records with trigger rules, prompt fragments and tool membership. They were managed through the console, with code providing the initial catalog. Hermes skills system

The product did not adopt autonomous rewriting of production procedures as its change-control mechanism. An operational policy about refunds or maintenance needs an accountable owner and evaluation before publication. Learning from a failure meant producing a case, a proposed change and a reviewable comparison.

A map of the selected patterns

Concern Reference pattern Product-specific implementation
Execution boundary OpenClaw harness lifecycle Stateless attempt contract and classified result
Runtime events Event-oriented execution Typed events with tenant, run, session and attempt identity
Agent capabilities Hermes skills and tool organisation Published skills composed with agent allowlists
Model access Provider abstraction Native adapter dispatch behind a shared stream contract
Conversation state Explicit session handling PostgreSQL run records and hospitality-specific state
Operator control Inspectable runtime surfaces Custom Next.js console for configuration, evals, traces and spend
Domain actions Tool-driven agents Backend bindings plus deterministic booking and dispatch flows

The custom work is concentrated in the right-hand column. A general agent framework cannot know that a guest has two active stays, that a task is only proposed, or that a quoted nightly price excludes checkout fees. Those rules belong to the product.

Modularity through contracts

The orchestrator depends on interfaces for the run store, router, context builder and harness. In Python, Protocol expresses this without requiring a deep inheritance tree.

from typing import Protocol

class ContextBuilder(Protocol):
    async def build(self, event, session, route): ...

class AttemptRunner(Protocol):
    async def run(self, attempt, sink): ...

class Harness:
    def __init__(self, runner: AttemptRunner):
        self.runner = runner

    async def run_attempt(self, attempt, sink):
        return await self.runner.run(attempt, sink)

The important property is what the object does not retain: no current_tenant, no current message array and no per-request sink. Those values are parameters. Shared dependencies can be long-lived; request data remains request-local.

This is a compact expression of the execution seam. The full product adds typed models, lifecycle events, cancellation and persistence around it. The modules remain understandable because each has a specific reason to change.

The composition root

Dependencies are wired during application startup in one composition root. It selects configured stores, constructs model transports and clients, loads registries, creates the router and context builder, and connects event sinks.

Requests borrow this graph. They do not reconstruct provider clients or rediscover tool handlers on every turn. Equally, changing the graph must not mutate another request’s input. Shared registries hold configuration; each attempt holds its own resolved input and policy.

Dependency wiring should make the active execution path explicit. A useful architecture review follows one request through identity, context, model access, tools and persistence. That exposes ownership boundaries and unnecessary coupling more clearly than a list of installed dependencies. In a modular harness, replacing a knowledge provider should change its adapter and contract tests, without changing the rules for tenancy or action approval.

The cost of owning a harness

A custom harness buys control over lifecycle, state and domain semantics. It also creates maintenance obligations: provider changes, tool contracts, schema migration, race conditions, configuration compatibility and evaluation infrastructure.

It is justified when those obligations already exist in the product and need explicit ownership. It is less attractive for a simple internal assistant that can operate safely inside an existing framework’s boundaries.

For this platform, the distinguishing requirements were operational action, shared tenancy and an inspectable control plane. Those requirements explain the architecture more convincingly than the phrase “custom agent framework.” The following chapters examine the contracts that made that architecture work.