glas-harness/docs/harness-contract.md
tegwick 97a50fc780
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Define the harness contract (GLAS-WP-0001-T01)
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>
2026-07-26 12:37:59 +02:00

86 lines
4 KiB
Markdown

# 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.