2026-07-26 12:37:59 +02:00
|
|
|
|
# Harness contract
|
|
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
Contract version: `1.0` (`glas_harness.contract.CONTRACT_VERSION`).
|
2026-07-26 12:37:59 +02:00
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
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.
|
|
|
|
|
|
|
2026-08-23 01:45:35 +02:00
|
|
|
|
`actor` is the governed sandbox consumer type and must be `adm`, `agt`, or
|
|
|
|
|
|
`atm`. It is not a worker instance identifier. Queue consumers retain values
|
|
|
|
|
|
such as `rein-aharness@railiance01` as upstream `worker_id` ownership metadata
|
|
|
|
|
|
and map governed agent execution to `actor="agt"`. Glas validates this before
|
|
|
|
|
|
sandbox creation and returns a normalized `resolution` refusal for invalid
|
|
|
|
|
|
channel/library requests; the CLI rejects invalid choices during argument
|
|
|
|
|
|
parsing.
|
|
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
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.
|
|
|
|
|
|
|
2026-08-23 11:32:23 +02:00
|
|
|
|
`HarnessProfile.status` governs catalog selection. The separate
|
|
|
|
|
|
`OperationalReadiness` model records runtime status, a reason, its owner, and
|
|
|
|
|
|
an evidence reference. `ready` requires positive evidence; `blocked` requires
|
|
|
|
|
|
an owner and evidence and is refused during resolution before sandbox
|
|
|
|
|
|
creation; `unverified` permits only a labeled proof attempt. The selected state
|
|
|
|
|
|
is copied into `ResolvedExecutionContext` and `ExecutionEvidence`.
|
|
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
## Rein lifecycle
|
2026-07-26 12:37:59 +02:00
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
class Rein(ABC):
|
|
|
|
|
|
def start_session(
|
|
|
|
|
|
self, profile: HarnessProfile, inputs: dict[str, str], sandbox: SandboxHandle
|
2026-08-21 10:40:29 +02:00
|
|
|
|
) -> dict[str, Any]: ...
|
2026-07-26 12:37:59 +02:00
|
|
|
|
|
|
|
|
|
|
def dispatch_tool(
|
2026-08-21 10:40:29 +02:00
|
|
|
|
self, session: dict[str, Any], tool_call: ToolCall
|
2026-08-21 00:21:53 +02:00
|
|
|
|
) -> ToolResult: ...
|
2026-07-26 12:37:59 +02:00
|
|
|
|
|
2026-08-21 10:40:29 +02:00
|
|
|
|
def end_session(self, session: dict[str, Any]) -> ExecutionSummary: ...
|
|
|
|
|
|
|
|
|
|
|
|
def cleanup_session(self, session: dict[str, Any]) -> None: ...
|
2026-07-26 12:37:59 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
```text
|
|
|
|
|
|
Glas rein sand-boxer
|
|
|
|
|
|
---- ---- ----------
|
|
|
|
|
|
resolve profile + rein descriptor
|
|
|
|
|
|
create sandbox ------------------------------------------> create
|
2026-08-21 10:40:29 +02:00
|
|
|
|
derive workspace + transport from handle
|
|
|
|
|
|
start_session(profile, inputs, handle) -> sandbox-local setup
|
|
|
|
|
|
dispatch_tool(session, call) -> inner loop across transport
|
2026-08-21 00:21:53 +02:00
|
|
|
|
end_session(session) -> normalized facts
|
2026-08-21 10:40:29 +02:00
|
|
|
|
cleanup_session(session) -> remove ephemeral task material
|
2026-08-21 00:21:53 +02:00
|
|
|
|
destroy sandbox -----------------------------------------> destroy
|
|
|
|
|
|
publish compact ExecutionEvidence
|
2026-07-26 12:37:59 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-21 10:40:29 +02:00
|
|
|
|
After sandbox creation, the caller's source checkout is no longer an execution
|
|
|
|
|
|
path. A same-host descriptor must contain `pid` plus `workspace_dir` and every
|
2026-09-05 19:16:22 +02:00
|
|
|
|
rein command uses the creating manager’s `execute` operation; a remote descriptor must contain `ssh`
|
2026-08-21 10:40:29 +02:00
|
|
|
|
plus `remote_dir` and every command crosses SSH. Incomplete, mixed, or unknown
|
|
|
|
|
|
reachability refuses at session start. The transport also bounds the outer rein
|
|
|
|
|
|
subprocess with the profile timeout. A host must make the selected rein command
|
|
|
|
|
|
and its dependencies available inside that transport; host-only installation is
|
|
|
|
|
|
not treated as sandbox availability.
|
|
|
|
|
|
|
2026-09-05 19:16:22 +02:00
|
|
|
|
The gateway binds local execution to the exact create-time consumer: actor,
|
|
|
|
|
|
project, and resolved request id as `run_id`. Every owner command forwards the
|
|
|
|
|
|
selected profile's value-free `credential_route_refs` and a timeout capped by
|
|
|
|
|
|
its limit. The binding is process-local and excluded from serialized handles.
|
|
|
|
|
|
An unbound local descriptor refuses execution; no consumer namespace entry or
|
|
|
|
|
|
host execution fallback exists. Owner timeouts and truncated output fail closed.
|
|
|
|
|
|
Generated task specs travel over stdin to an exclusive mode-0600 writer inside
|
|
|
|
|
|
`.git`; removal also crosses the owner boundary. Sand-boxer revision `b6655d8`
|
|
|
|
|
|
or a compatible implementation with bounded `stdin_text` is required.
|
|
|
|
|
|
|
|
|
|
|
|
Run `.venv/bin/python scripts/prove-owner-boundary.py` for a non-secret gateway
|
|
|
|
|
|
boundary proof. It injects deterministic dispatch in place of the agent loop
|
|
|
|
|
|
and does not establish production rein, credential, or egress readiness.
|
|
|
|
|
|
|
2026-08-23 12:53:05 +02:00
|
|
|
|
SSH reachability accepts only one non-option host or `user@host` target whose
|
|
|
|
|
|
components begin with an alphanumeric character. Glas also terminates SSH
|
|
|
|
|
|
option parsing with `--`; a reachability descriptor cannot reinterpret its
|
|
|
|
|
|
target as an SSH flag.
|
|
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
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.
|
2026-07-26 20:22:55 +02:00
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
## Stability and extension rules
|
2026-07-26 20:22:55 +02:00
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
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.
|
2026-07-26 20:22:55 +02:00
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
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.
|
2026-07-26 20:22:55 +02:00
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
Sensitive values are never contract extensions. Profiles may contain
|
|
|
|
|
|
credential-route references, while credential resolution and provider setup
|
|
|
|
|
|
remain rein-local under ADR-002.
|
2026-07-26 20:22:55 +02:00
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
## Failure semantics
|
|
|
|
|
|
|
|
|
|
|
|
The gateway returns evidence for every normal refusal/failure path:
|
|
|
|
|
|
|
2026-08-23 11:32:23 +02:00
|
|
|
|
- `resolution`: unknown, disabled, ambiguous, incompatible, operationally
|
|
|
|
|
|
blocked, or unsafe profile;
|
2026-08-21 00:21:53 +02:00
|
|
|
|
- `sandbox_create`;
|
|
|
|
|
|
- `session_start`;
|
|
|
|
|
|
- `execution` (including a rein-declared unsuccessful result);
|
|
|
|
|
|
- `session_end`; and
|
|
|
|
|
|
- `teardown`.
|
2026-07-26 20:22:55 +02:00
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
Profile resolution happens before sandbox creation. Once a sandbox exists,
|
2026-08-21 10:40:29 +02:00
|
|
|
|
rein cleanup and sandbox teardown are attempted on every path. `refused` means
|
|
|
|
|
|
governed execution did not proceed; `failed` means an attempted lifecycle did
|
|
|
|
|
|
not complete successfully.
|
2026-07-26 20:22:55 +02:00
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
## Deliberate non-goals
|
2026-07-26 12:37:59 +02:00
|
|
|
|
|
2026-08-21 00:21:53 +02:00
|
|
|
|
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.
|