Wire the gateway's own State Hub reporting (GLAS-WP-0002-T03)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

hub.py mirrors the two reins' hub.py under author: agt-glas-harness.
run_task_through_rein gains report_to_hub (default True), posting one
gateway_run progress event from a finally block -- fires on both
success and failure, giving the gateway an audit trail independent of
whatever the rein itself reports. cli.py gained a matching --no-hub
flag.

Live-verified: ran a real task through ReinOpenWeights with hub
reporting enabled, confirmed the gateway_run event landed with correct
detail and a real commit sha. 4 new tests, 23/23 passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-26 20:20:17 +02:00
parent 4d21b40cef
commit 2925538b93
6 changed files with 206 additions and 3 deletions

View file

@ -18,6 +18,7 @@ def main(argv: list[str] | None = None) -> int:
run.add_argument("--description", required=True)
run.add_argument("--actor", default="agt")
run.add_argument("--project", default="glas-harness")
run.add_argument("--no-hub", action="store_true", help="Skip the gateway's own hub reporting")
args = parser.parse_args(argv)
@ -31,6 +32,7 @@ def main(argv: list[str] | None = None) -> int:
description=args.description,
actor=args.actor,
project=args.project,
report_to_hub=not args.no_hub,
)
print(json.dumps(result, indent=2))
return 0 if result["tool_ok"] else 1

View file

@ -6,6 +6,13 @@ commit landed, tear the sandbox down. This is the parity proof gating
any later "retire rein-aharness as a standalone concern" conversation
it is not itself that conversation.
GLAS-WP-0002-T03: post the gateway's own State Hub event, independent of
whatever the rein itself reports (both reins' CLIs are invoked with
their own hub reporting disabled by their glas-harness adapters see
reins/rein_aharness.py / reins/rein_openweights.py). Reported on both
success and failure, from a `finally` block, so a raised exception still
leaves an audit trail.
Requires the `sandbox` extra (sand-boxer installed as a sibling
editable dependency).
"""
@ -17,6 +24,7 @@ from typing import Any
from sandboxer.core.manager import SandboxManager
from sandboxer.models import Consumer, SandboxCreateRequest
from glas_harness import hub
from glas_harness.contract import Rein, SandboxHandle, ToolCall
from glas_harness.reins.rein_aharness import ReinAharness
@ -31,6 +39,7 @@ def run_task_through_rein(
actor: str = "agt",
project: str = "glas-harness",
manager: SandboxManager | None = None,
report_to_hub: bool = True,
) -> dict[str, Any]:
"""Resolve `sandbox_profile`, run one task inside it via `rein`, verify, tear down.
@ -47,6 +56,8 @@ def run_task_through_rein(
consumer=Consumer(actor=actor, project=project),
)
status = manager.create(request)
result: dict[str, Any] | None = None
error: str | None = None
try:
reachability = status.reachability.model_dump(mode="json") if status.reachability else {}
sandbox = SandboxHandle(
@ -61,12 +72,56 @@ def run_task_through_rein(
tool_result = rein.dispatch_tool(session, ToolCall(name="run_task", actor=actor))
summary = rein.end_session(session)
return {
result = {
"sandbox_id": status.sandbox_id,
"tool_ok": tool_result.ok,
"tool_output": tool_result.output,
"tool_error": tool_result.error,
"summary": summary,
}
return result
except Exception as exc:
error = str(exc)
raise
finally:
manager.destroy(status.sandbox_id)
if report_to_hub:
_post_gateway_event(
rein=rein,
sandbox_profile=sandbox_profile,
sandbox_id=status.sandbox_id,
project=project,
actor=actor,
title=title,
result=result,
error=error,
)
def _post_gateway_event(
*,
rein: Rein,
sandbox_profile: str,
sandbox_id: str,
project: str,
actor: str,
title: str,
result: dict[str, Any] | None,
error: str | None,
) -> None:
ok = bool(result and result.get("tool_ok"))
hub.post_progress_event(
summary=f"gateway run: {title} ({'ok' if ok else 'failed'})",
event_type="gateway_run",
detail={
"sandbox_profile": sandbox_profile,
"sandbox_id": sandbox_id,
"rein": type(rein).__name__,
"project": project,
"actor": actor,
"task_title": title,
"ok": ok,
"result": result,
"error": error,
},
)

44
src/glas_harness/hub.py Normal file
View file

@ -0,0 +1,44 @@
"""Custodian State Hub reporting (REST, no MCP) — the gateway's own audit trail.
Mirrors rein-aharness's/rein-openweights's hub.py, but under glas-harness's
own actor attribution. Both reins' CLIs are invoked with their own hub
reporting disabled (`--no-hub`) by their glas-harness adapters this
module is what makes the gateway's audit trail exist independent of
which rein ran (GLAS-WP-0002-T03).
"""
from __future__ import annotations
import os
from typing import Any
import httpx
_DEFAULT_URL = "http://127.0.0.1:8000"
_TIMEOUT = 10.0
def _base_url() -> str:
return os.environ.get("STATE_HUB_URL", _DEFAULT_URL).rstrip("/")
def post_progress_event(
summary: str,
event_type: str,
detail: dict[str, Any],
task_id: str | None = None,
) -> bool:
payload: dict[str, Any] = {
"summary": summary,
"event_type": event_type,
"detail": detail,
"author": "agt-glas-harness",
}
if task_id:
payload["task_id"] = task_id
try:
resp = httpx.post(f"{_base_url()}/progress/", json=payload, timeout=_TIMEOUT)
resp.raise_for_status()
return True
except httpx.HTTPError:
return False