Some checks failed
ci / validate (push) Has been cancelled
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
146 lines
6.7 KiB
Markdown
146 lines
6.7 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.
|
||
|
||
`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.
|
||
|
||
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.
|
||
|
||
`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`.
|
||
|
||
## Rein lifecycle
|
||
|
||
```python
|
||
class Rein(ABC):
|
||
def start_session(
|
||
self, profile: HarnessProfile, inputs: dict[str, str], sandbox: SandboxHandle
|
||
) -> dict[str, Any]: ...
|
||
|
||
def dispatch_tool(
|
||
self, session: dict[str, Any], tool_call: ToolCall
|
||
) -> ToolResult: ...
|
||
|
||
def end_session(self, session: dict[str, Any]) -> ExecutionSummary: ...
|
||
|
||
def cleanup_session(self, session: dict[str, Any]) -> None: ...
|
||
```
|
||
|
||
```text
|
||
Glas rein sand-boxer
|
||
---- ---- ----------
|
||
resolve profile + rein descriptor
|
||
create sandbox ------------------------------------------> create
|
||
derive workspace + transport from handle
|
||
start_session(profile, inputs, handle) -> sandbox-local setup
|
||
dispatch_tool(session, call) -> inner loop across transport
|
||
end_session(session) -> normalized facts
|
||
cleanup_session(session) -> remove ephemeral task material
|
||
destroy sandbox -----------------------------------------> destroy
|
||
publish compact ExecutionEvidence
|
||
```
|
||
|
||
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
|
||
rein command uses the creating manager’s `execute` operation; a remote descriptor must contain `ssh`
|
||
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.
|
||
|
||
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.
|
||
|
||
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.
|
||
|
||
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, operationally
|
||
blocked, 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,
|
||
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.
|
||
|
||
## 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.
|