315 lines
9.7 KiB
Python
315 lines
9.7 KiB
Python
"""Profile-driven Glas gateway.
|
|
|
|
The gateway resolves a versioned harness profile before creating a sandbox.
|
|
There is deliberately no default rein or model in governed execution.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
import uuid
|
|
from datetime import UTC, datetime
|
|
|
|
from sandboxer.core.manager import SandboxManager
|
|
from sandboxer.models import Consumer, SandboxCreateRequest
|
|
|
|
from glas_harness import hub
|
|
from glas_harness.contract import (
|
|
CONTRACT_VERSION,
|
|
ExecutionEvidence,
|
|
ExecutionRequest,
|
|
ExecutionSummary,
|
|
GatewayResult,
|
|
Rein,
|
|
SandboxHandle,
|
|
ToolCall,
|
|
ToolResult,
|
|
)
|
|
from glas_harness.profiles import ProfileCatalog
|
|
|
|
|
|
def _now() -> str:
|
|
return datetime.now(UTC).isoformat()
|
|
|
|
|
|
def _request_refs(request: ExecutionRequest) -> dict:
|
|
refs = {
|
|
"assignment_ref": request.assignment_ref,
|
|
"role_ref": request.role_ref,
|
|
"duty_ref": request.duty_ref,
|
|
"goal_refs": request.goal_refs,
|
|
"resource_envelope_refs": request.resource_envelope_refs,
|
|
"expected_output": request.expected_output,
|
|
}
|
|
return {key: value for key, value in refs.items() if value not in (None, [], "")}
|
|
|
|
|
|
def run_execution(
|
|
request: ExecutionRequest,
|
|
*,
|
|
catalog: ProfileCatalog | None = None,
|
|
rein: Rein | None = None,
|
|
manager: SandboxManager | None = None,
|
|
) -> GatewayResult:
|
|
"""Resolve and run one request, returning evidence for every outcome."""
|
|
|
|
request_id = request.request_id or str(uuid.uuid4())
|
|
started_at = _now()
|
|
started = time.monotonic()
|
|
catalog = catalog or ProfileCatalog()
|
|
refs = _request_refs(request)
|
|
|
|
profile = None
|
|
descriptor = None
|
|
sandbox_id: str | None = None
|
|
tool_result: ToolResult | None = None
|
|
summary: ExecutionSummary | None = None
|
|
outcome = "failed"
|
|
failure_stage = None
|
|
error: str | None = None
|
|
|
|
try:
|
|
profile, descriptor = catalog.resolve(request.harness_profile_ref)
|
|
selected_rein = rein or catalog.build_rein(profile, descriptor)
|
|
except Exception as exc:
|
|
outcome = "refused"
|
|
failure_stage = "resolution"
|
|
error = str(exc)
|
|
result = _build_result(
|
|
request=request,
|
|
request_id=request_id,
|
|
started_at=started_at,
|
|
started=started,
|
|
outcome=outcome,
|
|
failure_stage=failure_stage,
|
|
error=error,
|
|
profile=profile,
|
|
descriptor=descriptor,
|
|
sandbox_id=None,
|
|
tool_result=None,
|
|
summary=None,
|
|
refs=refs,
|
|
)
|
|
_report(request, result)
|
|
return result
|
|
|
|
manager = manager or SandboxManager()
|
|
status = None
|
|
try:
|
|
try:
|
|
status = manager.create(
|
|
SandboxCreateRequest(
|
|
profile=profile.sandbox_profile,
|
|
inputs={"repo": request.repo},
|
|
consumer=Consumer(actor=request.actor, project=request.project),
|
|
ttl=None,
|
|
)
|
|
)
|
|
sandbox_id = status.sandbox_id
|
|
except Exception as exc:
|
|
failure_stage = "sandbox_create"
|
|
error = str(exc)
|
|
raise
|
|
|
|
reachability = (
|
|
status.reachability.model_dump(mode="json", exclude_none=True)
|
|
if status.reachability
|
|
else {}
|
|
)
|
|
sandbox = SandboxHandle(
|
|
sandbox_id=status.sandbox_id,
|
|
host=status.host or "",
|
|
reachability=reachability,
|
|
)
|
|
|
|
try:
|
|
session = selected_rein.start_session(
|
|
profile,
|
|
{
|
|
"title": request.title,
|
|
"description": request.description,
|
|
"target_repo": request.repo,
|
|
"request_id": request_id,
|
|
},
|
|
sandbox,
|
|
)
|
|
except Exception as exc:
|
|
failure_stage = "session_start"
|
|
error = str(exc)
|
|
raise
|
|
|
|
try:
|
|
tool_result = selected_rein.dispatch_tool(
|
|
session, ToolCall(name="run_task", actor=request.actor)
|
|
)
|
|
except Exception as exc:
|
|
failure_stage = "execution"
|
|
error = str(exc)
|
|
raise
|
|
|
|
try:
|
|
summary = selected_rein.end_session(session)
|
|
except Exception as exc:
|
|
failure_stage = "session_end"
|
|
error = str(exc)
|
|
raise
|
|
|
|
if tool_result.ok and summary.outcome == "succeeded":
|
|
outcome = "succeeded"
|
|
else:
|
|
outcome = summary.outcome if summary.outcome in {"failed", "refused"} else "failed"
|
|
failure_stage = "execution"
|
|
error = tool_result.error or summary.reason or "rein reported unsuccessful execution"
|
|
except Exception:
|
|
# The exact error/stage is captured above. All paths still tear down and
|
|
# return normalized evidence rather than leaking a provider exception.
|
|
pass
|
|
finally:
|
|
if status is not None:
|
|
try:
|
|
manager.destroy(status.sandbox_id)
|
|
except Exception as exc:
|
|
if outcome == "succeeded" or not error:
|
|
outcome = "failed"
|
|
failure_stage = "teardown"
|
|
error = str(exc)
|
|
|
|
result = _build_result(
|
|
request=request,
|
|
request_id=request_id,
|
|
started_at=started_at,
|
|
started=started,
|
|
outcome=outcome,
|
|
failure_stage=failure_stage,
|
|
error=error,
|
|
profile=profile,
|
|
descriptor=descriptor,
|
|
sandbox_id=sandbox_id,
|
|
tool_result=tool_result,
|
|
summary=summary,
|
|
refs=refs,
|
|
)
|
|
_report(request, result)
|
|
return result
|
|
|
|
|
|
def run_task_through_rein(
|
|
*,
|
|
harness_profile: str,
|
|
repo: str,
|
|
title: str,
|
|
description: str,
|
|
rein: Rein | None = None,
|
|
actor: str = "agt",
|
|
project: str = "glas-harness",
|
|
manager: SandboxManager | None = None,
|
|
catalog: ProfileCatalog | None = None,
|
|
report_to_hub: bool = True,
|
|
) -> dict:
|
|
"""Compatibility-shaped wrapper around the versioned execution request.
|
|
|
|
It intentionally requires a harness profile. Direct rein injection is
|
|
retained only as a library/test seam and never selects a default backend.
|
|
"""
|
|
|
|
result = run_execution(
|
|
ExecutionRequest(
|
|
harness_profile_ref=harness_profile,
|
|
repo=repo,
|
|
title=title,
|
|
description=description,
|
|
actor=actor,
|
|
project=project,
|
|
report_to_hub=report_to_hub,
|
|
),
|
|
catalog=catalog,
|
|
rein=rein,
|
|
manager=manager,
|
|
)
|
|
return result.model_dump(mode="json")
|
|
|
|
|
|
def _build_result(
|
|
*,
|
|
request: ExecutionRequest,
|
|
request_id: str,
|
|
started_at: str,
|
|
started: float,
|
|
outcome: str,
|
|
failure_stage: str | None,
|
|
error: str | None,
|
|
profile,
|
|
descriptor,
|
|
sandbox_id: str | None,
|
|
tool_result: ToolResult | None,
|
|
summary: ExecutionSummary | None,
|
|
refs: dict,
|
|
) -> GatewayResult:
|
|
duration = max(0.0, time.monotonic() - started)
|
|
resolved_model = (
|
|
(summary.resolved_model if summary else None)
|
|
or (tool_result.resolved_model if tool_result else None)
|
|
or (profile.model.model if profile else None)
|
|
)
|
|
tokens_spent = (
|
|
summary.tokens_spent if summary and summary.tokens_spent is not None
|
|
else tool_result.tokens_spent if tool_result else None
|
|
)
|
|
execution_duration = (
|
|
summary.duration_s if summary and summary.duration_s is not None
|
|
else tool_result.duration_s if tool_result else None
|
|
)
|
|
evidence_error = error if failure_stage == "resolution" else (
|
|
f"{failure_stage} failed; inspect direct caller error" if error and failure_stage else None
|
|
)
|
|
evidence = ExecutionEvidence(
|
|
request_id=request_id,
|
|
correlation_id=request.correlation_id,
|
|
actor=request.actor,
|
|
project=request.project,
|
|
target_repo=request.repo,
|
|
contract_version=CONTRACT_VERSION,
|
|
profile_ref=str(profile.ref) if profile else None,
|
|
rein_id=descriptor.id if descriptor else None,
|
|
rein_version=descriptor.version if descriptor else None,
|
|
model_route=profile.model.route if profile else None,
|
|
resolved_model=resolved_model,
|
|
sandbox_profile=profile.sandbox_profile if profile else None,
|
|
sandbox_id=sandbox_id,
|
|
tool_profile=profile.tool_profile if profile else None,
|
|
outcome=outcome,
|
|
failure_stage=failure_stage,
|
|
error=evidence_error,
|
|
started_at=started_at,
|
|
finished_at=_now(),
|
|
duration_s=execution_duration if execution_duration is not None else duration,
|
|
tokens_spent=tokens_spent,
|
|
token_budget=profile.limits.budget_tokens if profile else None,
|
|
commit_sha=summary.commit_sha if summary else None,
|
|
artifacts=summary.artifacts if summary else [],
|
|
tool_events_count=len(tool_result.events) if tool_result else 0,
|
|
tool_events_completeness=(
|
|
tool_result.events_completeness if tool_result else "unavailable"
|
|
),
|
|
refs=refs,
|
|
)
|
|
return GatewayResult(
|
|
ok=outcome == "succeeded",
|
|
evidence=evidence,
|
|
tool_output=tool_result.output if tool_result else "",
|
|
tool_error=error or (tool_result.error if tool_result else None),
|
|
)
|
|
|
|
|
|
def _report(request: ExecutionRequest, result: GatewayResult) -> None:
|
|
if not request.report_to_hub:
|
|
return
|
|
evidence = result.evidence.model_dump(mode="json", exclude_none=True)
|
|
hub.post_progress_event(
|
|
summary=(
|
|
f"gateway run: {request.title} "
|
|
f"({'ok' if result.ok else result.evidence.outcome})"
|
|
),
|
|
event_type="gateway_run",
|
|
detail=evidence,
|
|
)
|