Wire the gateway's own State Hub reporting (GLAS-WP-0002-T03)
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:
parent
4d21b40cef
commit
2925538b93
6 changed files with 206 additions and 3 deletions
|
|
@ -10,6 +10,7 @@
|
|||
| --- | --- | --- | --- | --- |
|
||||
| workplan | GLAS-0001 | active | — | workplans/GLAS-0001-statehub-bootstrap.md |
|
||||
| workplan | GLAS-WP-0001 | active | — | workplans/GLAS-WP-0001-harness-router-foundation.md |
|
||||
| workplan | GLAS-WP-0002 | proposed | — | workplans/GLAS-WP-0002-observability-and-composability-followups.md |
|
||||
| task | GLAS-0001-T01 | done | — | workplans/GLAS-0001-statehub-bootstrap.md |
|
||||
| task | GLAS-0001-T02 | done | — | workplans/GLAS-0001-statehub-bootstrap.md |
|
||||
| task | GLAS-0001-T03 | done | — | workplans/GLAS-0001-statehub-bootstrap.md |
|
||||
|
|
@ -19,3 +20,7 @@
|
|||
| task | GLAS-WP-0001-T04 | done | — | workplans/GLAS-WP-0001-harness-router-foundation.md |
|
||||
| task | GLAS-WP-0001-T05 | done | — | workplans/GLAS-WP-0001-harness-router-foundation.md |
|
||||
| task | GLAS-WP-0001-T06 | done | — | workplans/GLAS-WP-0001-harness-router-foundation.md |
|
||||
| task | GLAS-WP-0002-T01 | todo | — | workplans/GLAS-WP-0002-observability-and-composability-followups.md |
|
||||
| task | GLAS-WP-0002-T02 | todo | — | workplans/GLAS-WP-0002-observability-and-composability-followups.md |
|
||||
| task | GLAS-WP-0002-T03 | todo | — | workplans/GLAS-WP-0002-observability-and-composability-followups.md |
|
||||
| task | GLAS-WP-0002-T04 | todo | — | workplans/GLAS-WP-0002-observability-and-composability-followups.md |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
44
src/glas_harness/hub.py
Normal 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
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
from datetime import UTC, datetime
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from sandboxer.models import Reachability, SandboxState, SandboxStatus
|
||||
|
||||
|
|
@ -54,6 +54,7 @@ def test_run_task_through_rein_creates_and_destroys_sandbox() -> None:
|
|||
description="d",
|
||||
rein=rein,
|
||||
manager=manager,
|
||||
report_to_hub=False,
|
||||
)
|
||||
|
||||
assert rein.calls == ["start_session", "dispatch_tool", "end_session"]
|
||||
|
|
@ -81,8 +82,84 @@ def test_run_task_through_rein_destroys_sandbox_even_on_failure() -> None:
|
|||
description="d",
|
||||
rein=rein,
|
||||
manager=manager,
|
||||
report_to_hub=False,
|
||||
)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
manager.destroy.assert_called_once_with("sbx1")
|
||||
|
||||
|
||||
def test_run_task_through_rein_reports_success_event() -> None:
|
||||
manager = MagicMock()
|
||||
manager.create.return_value = _fake_status()
|
||||
rein = _FakeRein()
|
||||
|
||||
with patch("glas_harness.gateway.hub.post_progress_event", return_value=True) as post:
|
||||
run_task_through_rein(
|
||||
sandbox_profile="profile.bwrap-local",
|
||||
repo="/tmp/repo",
|
||||
title="t",
|
||||
description="d",
|
||||
rein=rein,
|
||||
manager=manager,
|
||||
)
|
||||
|
||||
post.assert_called_once()
|
||||
kwargs = post.call_args.kwargs
|
||||
assert kwargs["event_type"] == "gateway_run"
|
||||
assert "ok)" in kwargs["summary"]
|
||||
assert kwargs["detail"]["ok"] is True
|
||||
assert kwargs["detail"]["sandbox_id"] == "sbx1"
|
||||
assert kwargs["detail"]["rein"] == "_FakeRein"
|
||||
|
||||
|
||||
def test_run_task_through_rein_reports_failure_event_and_still_raises() -> None:
|
||||
manager = MagicMock()
|
||||
manager.create.return_value = _fake_status()
|
||||
|
||||
class _FailingRein(_FakeRein):
|
||||
def dispatch_tool(self, session, tool_call):
|
||||
raise RuntimeError("boom")
|
||||
|
||||
rein = _FailingRein()
|
||||
|
||||
with patch("glas_harness.gateway.hub.post_progress_event", return_value=True) as post:
|
||||
try:
|
||||
run_task_through_rein(
|
||||
sandbox_profile="profile.bwrap-local",
|
||||
repo="/tmp/repo",
|
||||
title="t",
|
||||
description="d",
|
||||
rein=rein,
|
||||
manager=manager,
|
||||
)
|
||||
assert False, "expected RuntimeError to propagate"
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
post.assert_called_once()
|
||||
kwargs = post.call_args.kwargs
|
||||
assert "failed)" in kwargs["summary"]
|
||||
assert kwargs["detail"]["ok"] is False
|
||||
assert kwargs["detail"]["error"] == "boom"
|
||||
assert kwargs["detail"]["result"] is None
|
||||
|
||||
|
||||
def test_run_task_through_rein_skips_hub_when_disabled() -> None:
|
||||
manager = MagicMock()
|
||||
manager.create.return_value = _fake_status()
|
||||
rein = _FakeRein()
|
||||
|
||||
with patch("glas_harness.gateway.hub.post_progress_event") as post:
|
||||
run_task_through_rein(
|
||||
sandbox_profile="profile.bwrap-local",
|
||||
repo="/tmp/repo",
|
||||
title="t",
|
||||
description="d",
|
||||
rein=rein,
|
||||
manager=manager,
|
||||
report_to_hub=False,
|
||||
)
|
||||
|
||||
post.assert_not_called()
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
id: GLAS-WP-0002
|
||||
title: "Observability and composability follow-ups"
|
||||
status: proposed
|
||||
state_hub_workstream_id: "ec0777ff-18ce-4666-abaf-5bd2ff0fd4b9"
|
||||
---
|
||||
|
||||
Four items surfaced as open-but-not-urgent while closing out
|
||||
|
|
@ -33,6 +34,7 @@ this same task — not before.
|
|||
id: GLAS-WP-0002-T01
|
||||
status: todo
|
||||
priority: medium
|
||||
state_hub_task_id: "2de6074e-b72b-4eb7-b454-6b849011348c"
|
||||
```
|
||||
|
||||
## Task: Live-verify rein-openweights's OpenBao credential path
|
||||
|
|
@ -53,6 +55,7 @@ both repos.
|
|||
id: GLAS-WP-0002-T02
|
||||
status: todo
|
||||
priority: medium
|
||||
state_hub_task_id: "74b4f35b-9cdd-4228-aeed-e0eec458c0bf"
|
||||
```
|
||||
|
||||
## Task: Wire glas-harness's own State Hub reporting from the gateway
|
||||
|
|
@ -67,10 +70,26 @@ what makes the *gateway's* audit trail exist independent of which rein
|
|||
ran, matching the "observable by default" design principle in
|
||||
`INTENT.md`.
|
||||
|
||||
**Done (2026-07-26).** `src/glas_harness/hub.py` (mirrors the two
|
||||
reins' hub.py under `author: agt-glas-harness`); `gateway.py`'s
|
||||
`run_task_through_rein` gains `report_to_hub: bool = True`, posting one
|
||||
`gateway_run` progress event from a `finally` block — fires on both
|
||||
success and failure (a raised exception still leaves an audit trail),
|
||||
with `ok`/`rein`/`sandbox_id`/`sandbox_profile`/`project`/`result`/
|
||||
`error` in the detail. `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 in State Hub with the
|
||||
correct detail (`author: agt-glas-harness`, real commit sha in
|
||||
`result`). Token events skipped for now — `ToolResult` doesn't carry a
|
||||
uniform token count across reins, and it wasn't worth forcing one just
|
||||
for this task. 4 new tests (success/failure/disabled paths), 23/23
|
||||
passing.
|
||||
|
||||
```task
|
||||
id: GLAS-WP-0002-T03
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "76462175-4a85-4552-a6c1-af871cc1b8d3"
|
||||
```
|
||||
|
||||
## Task: First slice of glas-harness's remaining charter pillars
|
||||
|
|
@ -92,4 +111,5 @@ indefinitely.
|
|||
id: GLAS-WP-0002-T04
|
||||
status: todo
|
||||
priority: low
|
||||
state_hub_task_id: "98d4be61-5923-4445-b1f8-5135ada13ad9"
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue