glas-harness/docs/harness-contract.md
tegwick 356e34992a
Some checks failed
ci / validate (push) Has been cancelled
feat: expose cleanup and reporting outcomes for gateway runs
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
2026-09-06 11:12:30 +02:00

171 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 managers `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.
## Cleanup and reporting outcomes (GLAS-WP-0013)
`ExecutionEvidence.session_cleanup` and `sandbox_destroy` independently report
`not_attempted`, `succeeded` or `failed`. If execution fails and cleanup also
fails, the original failure stage/error remains primary and both teardown
outcomes remain visible. Destroy is attempted even after cleanup failure.
A teardown failure following successful execution makes the run unsuccessful.
A successful method return is the owner's reported result, not an additional
filesystem/process verification by the gateway.
`GatewayResult.hub_report_status` reports `not_requested`, `accepted` or `failed`
to the direct caller (including CLI JSON). `accepted` means the progress HTTP
request returned successfully. It does not guarantee durable audit storage or
exactly-once delivery. A timeout may occur after the server accepted a request;
`failed` means acknowledgement was not obtained. Reporting failure does not
rerun the agent or change its execution outcome. There is no automatic retry
or durable outbox. The status is outside ExecutionEvidence because it is known
only after the evidence report is attempted.
These are additive defaulted fields in the current contract models. Old records
without them load as `unknown`, rather than implying that cleanup or reporting
occurred. Consumers using older strict schemas must update to accept these
fields before consuming new serialized results. No profile pins or readiness
states change with this addition.