# 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 ```python 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_session` resolves 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_tool` enforces the tool-profile allow-list glas-harness resolved (the same allow-list concept rein-aharness's `profiles.py` already implements today, just invoked through the contract instead of only the rein's own CLI). - `end_session` is 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_session` also carries scheduling/blueprint-sourcing parameters (kaizen-agentic blueprint id, activity-core task id) as part of `inputs`, or whether that stays entirely rein-side — tracked in `rein-aharness/workplans/HARNESS-WP-0002-T04`. - The exact `SandboxHandle`/`ToolCall`/`ToolResult` field shapes — sketched here at the level needed to unblock `GLAS-WP-0001-T04` (the rein-aharness parity proof); refine once that task is underway rather than speculatively now.