start_session/dispatch_tool/end_session, mirroring sand-boxer's SandboxExtension ABC one layer up: sand-boxer establishes where work runs, this contract governs how an agent session runs on top of it. Unblocks the rein-aharness parity proof (T04) and rein-openweights bootstrap (T05). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4 KiB
Harness contract
The interface a concrete harness backend ("rein") implements so glas-harness
can route to it, per docs/adr/ADR-001-rein-harness-family.md. Mirrors
sand-boxer's SandboxExtension ABC (provision/wait_ready/teardown) at
the harness level, one layer up: sand-boxer establishes where dangerous
work runs, this contract governs how an agent session runs on top of it.
Interface
class Rein(ABC):
"""Base class for concrete harness backends (reins)."""
@abstractmethod
def start_session(
self, profile: HarnessProfile, inputs: dict[str, str], sandbox: SandboxHandle
) -> dict[str, str]:
"""Begin an agent session bound to a sandbox. Returns a session handle."""
@abstractmethod
def dispatch_tool(
self, session: dict[str, str], tool_call: ToolCall
) -> ToolResult:
"""Run one tool call under the session's policy. Returns result + audit envelope."""
@abstractmethod
def end_session(self, session: dict[str, str]) -> dict[str, str]:
"""Close the session. Returns a summary (commit sha, tokens, duration, outcome)."""
start_sessionresolves the rein's own setup (persona/blueprint loading, credential acquisition) against the sandbox handle glas-harness already obtained from sand-boxer — the rein never provisions or tears down a sandbox itself.dispatch_toolenforces the tool-profile allow-list glas-harness resolved (the same allow-list concept rein-aharness'sprofiles.pyalready implements today, just invoked through the contract instead of only the rein's own CLI).end_sessionis where the existing "commit exists = success" signal (rein-aharness) or an equivalent per-rein success criterion is reported back to glas-harness for State Hub posting. glas-harness posts the audit event; the rein returns the facts.
Session lifecycle (who calls what)
glas-harness rein sand-boxer
──────────── ──── ──────────
resolve harness.* profile
request sandbox ──────────────────────────────▶ create()
receive sandbox handle ◀──────────────────────────────
start_session(profile, in, sbx) ─▶ acquire creds, load persona
◀─ session handle
dispatch_tool(session, call) ─▶ run under allow-list
◀─ result + audit envelope
(repeat dispatch_tool ...)
end_session(session) ─▶ verify success, summarize
◀─ summary
post State Hub event
request sandbox teardown ──────────────────────────────▶ destroy()
glas-harness owns the outer loop (profile resolution, sandbox
request/teardown, State Hub reporting, actor attribution). The rein owns
the inner loop (its own agentic reasoning, whatever that looks like —
Claude Code CLI subprocess for rein-aharness, a direct OpenRouter
tool-use loop for rein-openweights).
Actor attribution
Every dispatch_tool call and lifecycle transition carries an actor
(adm/agt/atm) per glas-harness INTENT.md's audit requirement.
glas-harness stamps this at dispatch time; reins do not need their own
actor model.
Open questions this contract does not resolve yet
- Whether
start_sessionalso carries scheduling/blueprint-sourcing parameters (kaizen-agentic blueprint id, activity-core task id) as part ofinputs, or whether that stays entirely rein-side — tracked inrein-aharness/workplans/HARNESS-WP-0002-T04. - The exact
SandboxHandle/ToolCall/ToolResultfield shapes — sketched here at the level needed to unblockGLAS-WP-0001-T04(the rein-aharness parity proof); refine once that task is underway rather than speculatively now.