Agent Harness Runtime Scaffold
This scaffold captures the runtime abstraction approved in the Slack thread: Humanwork owns the API contract, prompt/config, org context, approval policy, and persistence boundary; Hermes, Claw, PI, or another engine plugs in behind a runtime interface.
Cut Line
Above the harness, the Humanwork API stays responsible for routing, auth, org resolution, quotas, billing, and durable platform persistence.
The harness is the reusable lifecycle layer:
createAgentSession(...)creates a fresh per-call session.createAgentSessionRuntime(...)owns the current session through a small PI-style lifecycle wrapper, with hooks for session start, switch, shutdown, and replacement.SessionManager.inMemory()keeps pure ephemeral sessions.SessionManager.jsonl(root)keeps PI-style durable session logs.session.subscribe(...)exposes lifecycle, message, tool, approval, and error events to the platform.toolCallHandlersprovide PI-style pre-tool interception. Handlers can mutate tool input or block execution before the normal approval policy runs.session.prompt(message)runs the runtime loop until completion, approval pause, or failure.
Below the harness, an AgentRuntime adapter owns execution. Hermes is one
adapter, not the harness itself.
The first concrete adapter is HermesAgentServiceRuntime, which calls the
existing Python agent service /v1/chat contract. That keeps the current
Hermes CLI implementation in place while proving the harness can drive the
working system.
Prompt And Config Ownership
The platform side supplies systemPrompt, allowed tools, orgContext, and
approvalPolicy when creating the session. The runtime receives those values as
input; it does not define the Humanwork product contract.
import { createAgentSession, SessionManager } from '@humanwork/agent-harness';
const { session } = await createAgentSession({
sessionId: conversationId,
sessionManager: SessionManager.jsonl('/workspaces/acmefinancial/sessions'),
runtime: hermesRuntime,
systemPrompt: orgConfig.systemPrompt,
orgContext: {
orgId,
specialistId,
conversationId,
},
tools: orgConfig.allowedTools,
approvalPolicy: {
mode: 'risk',
gatedRisks: ['high', 'destructive'],
},
});
session.subscribe((event) => {
if (event.type === 'message_update') {
// Stream to Slack/platform.
}
if (event.type === 'approval_required') {
// Persist pending step and ask the expert.
}
});
const result = await session.prompt(userMessage);
For hosts that need to own session replacement separately from prompt execution, use the runtime wrapper:
import {
createAgentSessionRuntime,
SessionManager,
} from '@humanwork/agent-harness';
const runtime = await createAgentSessionRuntime(
{
sessionId: conversationId,
sessionManager: SessionManager.jsonl('/workspaces/acmefinancial/sessions'),
runtime: hermesRuntime,
systemPrompt: orgConfig.systemPrompt,
},
{
hooks: [
(event) => {
if (event.type === 'session_shutdown') {
// Detach host-owned streams or UI before switching/dispose.
}
},
],
},
);
await runtime.prompt(userMessage);
Hermes Agent Service Smoke
Start the existing Python agent service, then run:
AGENT_SERVICE_URL=http://localhost:8000 \
AGENT_SERVICE_SECRET="$AGENT_SERVICE_SECRET" \
bun run agent-harness:smoke -- "Can you confirm the harness can reach Hermes?"
That path is:
@humanwork/agent-harness
-> HermesAgentServiceRuntime
-> Python agent service /v1/chat
-> agent/hermes_client.py
-> real hermes chat CLI
The smoke writes a PI-style session log under .agent-harness-sessions/ by
default. Override it with HARNESS_SESSION_LOG_DIR.
Approval Modes
auto is the default and runs the full loop without pausing.
risk pauses only for configured tools or risks, for example destructive
external writes.
step pauses before every tool execution. This supports the issue #172 style
expert approval flow without making it mandatory for normal tasks.
State Model
The harness is process-ephemeral, not state-free. Session state is represented as an ordered log. For the MVP scaffold, that log can be in-memory or JSONL on disk. The Humanwork API can later flush JSONL into Postgres/R2 without making the runtime adapter aware of platform storage.
Current Location
The scaffold lives in packages/agent-harness as a standalone package. The repo
is wired as a Bun/Turborepo workspace, so callers can import it as
@humanwork/agent-harness once the package is consumed by the API or another
runtime host.