glas-harness/docs/harness-contract.md
tegwick 1cd890d871
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
feat: add versioned execution profiles
2026-08-21 00:21:53 +02:00

97 lines
3.8 KiB
Markdown

# Harness contract
Contract version: `1.0` (`glas_harness.contract.CONTRACT_VERSION`).
This is the stable boundary between Glas, its consumers, and concrete harness
backends (reins). Glas owns profile resolution and the outer lifecycle; a rein
owns its inner agentic loop. Sand-boxer owns environment provisioning.
## Consumer boundary
`ExecutionRequest` requires an explicit `harness_profile_ref`, repository,
title, and description. It may carry correlation and organizational references
(`assignment_ref`, `role_ref`, `duty_ref`, `goal_refs`, and
`resource_envelope_refs`). Those references link execution evidence to
leadership/workforce records; they do not transfer organizational authority to
Glas.
The gateway returns `GatewayResult` with:
- `ok`, derived from normalized outcome;
- `evidence`, safe for compact State Hub reporting; and
- direct-caller-only `tool_output` and `tool_error`.
Raw prompts, model output, tool output, and credential material are excluded
from the State Hub detail.
## Rein lifecycle
```python
class Rein(ABC):
def start_session(
self, profile: HarnessProfile, inputs: dict[str, str], sandbox: SandboxHandle
) -> dict[str, str]: ...
def dispatch_tool(
self, session: dict[str, str], tool_call: ToolCall
) -> ToolResult: ...
def end_session(self, session: dict[str, str]) -> ExecutionSummary: ...
```
```text
Glas rein sand-boxer
---- ---- ----------
resolve profile + rein descriptor
create sandbox ------------------------------------------> create
start_session(profile, inputs, handle) -> rein setup
dispatch_tool(session, call) -> inner loop
end_session(session) -> normalized facts
destroy sandbox -----------------------------------------> destroy
publish compact ExecutionEvidence
```
There is no production default rein. Direct `Rein` injection remains a narrow
library/test seam but still requires a valid profile so profile, sandbox,
model, tool policy, and evidence are explicit.
## Stability and extension rules
All contract models use Pydantic with unknown fields forbidden. Stable `1.0`
fields are the declared fields in `src/glas_harness/contract.py`. Optional
measurements such as tokens, duration, resolved model, commit, and artifacts
remain `null` when unknown; unsupported tool-event visibility is
`unavailable`, not an empty claim of completeness.
Rein-specific, non-secret additions belong only in declared `metadata` fields.
Adding a required field, changing a field meaning/type, or removing a field
requires a new contract version. Adding an optional field may remain compatible
only when old consumers can safely ignore it and every strict boundary model is
updated together. A profile and rein descriptor must both declare the exact
supported contract version.
Sensitive values are never contract extensions. Profiles may contain
credential-route references, while credential resolution and provider setup
remain rein-local under ADR-002.
## Failure semantics
The gateway returns evidence for every normal refusal/failure path:
- `resolution`: unknown, disabled, ambiguous, incompatible, or unsafe profile;
- `sandbox_create`;
- `session_start`;
- `execution` (including a rein-declared unsuccessful result);
- `session_end`; and
- `teardown`.
Profile resolution happens before sandbox creation. Once a sandbox exists,
teardown is attempted on every path. `refused` means governed execution did not
proceed; `failed` means an attempted lifecycle did not complete successfully.
## Deliberate non-goals
The contract does not schedule work, source blueprints, allocate workers,
resolve leadership, broker credentials, select an unapproved model by price, or
standardize rein internals. Composable rein middleware remains deferred by
ADR-004 until a second concrete need exists.