feat: capture worker artifacts before teardown and carry native limits
All checks were successful
ci / validate (push) Successful in 2m39s

Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
tegwick 2026-09-09 12:36:15 +02:00
parent 44991600f3
commit fb57c0228f
9 changed files with 159 additions and 3 deletions

View file

@ -46,7 +46,8 @@ class ModelRoute(ContractModel):
class ExecutionLimits(ContractModel):
budget_tokens: int | None = Field(default=None, gt=0)
timeout_seconds: int | None = Field(default=None, gt=0)
max_turns: int | None = Field(default=None, gt=0)
max_turns: int | None = Field(default=None, gt=0, strict=True)
max_budget_usd: float | None = Field(default=None, gt=0, allow_inf_nan=False, strict=True)
class ReinSelection(ContractModel):
@ -136,6 +137,7 @@ class ToolResult(ContractModel):
events: list[dict[str, Any]] = Field(default_factory=list)
events_completeness: Literal["complete", "partial", "unavailable"] = "unavailable"
tokens_spent: int | None = Field(default=None, ge=0)
cost_usd: float | None = Field(default=None, ge=0, allow_inf_nan=False)
duration_s: float | None = Field(default=None, ge=0)
resolved_model: str | None = None
metadata: dict[str, Any] = Field(default_factory=dict)
@ -147,6 +149,7 @@ class ExecutionSummary(ContractModel):
outcome: Literal["succeeded", "failed", "refused"]
reason: str | None = None
tokens_spent: int | None = Field(default=None, ge=0)
cost_usd: float | None = Field(default=None, ge=0, allow_inf_nan=False)
duration_s: float | None = Field(default=None, ge=0)
resolved_model: str | None = None
artifacts: list[str] = Field(default_factory=list)
@ -203,13 +206,14 @@ class ExecutionEvidence(ContractModel):
tool_profile: str | None = None
outcome: Literal["succeeded", "failed", "refused"]
failure_stage: Literal[
"resolution", "sandbox_create", "session_start", "execution", "session_end", "teardown"
"resolution", "sandbox_create", "session_start", "execution", "session_end", "artifact_capture", "teardown"
] | None = None
error: str | None = None
started_at: str
finished_at: str
duration_s: float = Field(ge=0)
tokens_spent: int | None = Field(default=None, ge=0)
cost_usd: float | None = Field(default=None, ge=0, allow_inf_nan=False)
token_budget: int | None = Field(default=None, ge=0)
commit_sha: str | None = None
artifacts: list[str] = Field(default_factory=list)

View file

@ -7,6 +7,7 @@ There is deliberately no default rein or model in governed execution.
from __future__ import annotations
import time
from collections.abc import Callable
import uuid
from datetime import UTC, datetime
@ -63,6 +64,7 @@ def run_execution(
catalog: ProfileCatalog | None = None,
rein: Rein | None = None,
manager: SandboxManager | None = None,
artifact_capture: Callable[[SandboxHandle, ExecutionSummary], None] | None = None,
) -> GatewayResult:
"""Resolve and run one request, returning evidence for every outcome."""
@ -209,6 +211,13 @@ def run_execution(
outcome = "failed"
failure_stage = "teardown"
error = str(exc)
if artifact_capture is not None and outcome == "succeeded" and summary is not None:
try:
artifact_capture(sandbox, summary)
except Exception as exc:
outcome = "failed"
failure_stage = "artifact_capture"
error = str(exc)
if status is not None:
try:
manager.destroy(status.sandbox_id)
@ -336,6 +345,7 @@ def _build_result(
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,
cost_usd=summary.cost_usd if summary and summary.cost_usd is not None else tool_result.cost_usd if tool_result 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,

View file

@ -178,6 +178,9 @@ class ProfileCatalog:
f"gateway supports {CONTRACT_VERSION}"
)
if profile.limits.max_budget_usd is not None and profile.rein.id != "rein-aharness":
raise IncompatibleProfileError("native USD limits are implemented only by rein-aharness")
descriptor = self.reins().get(profile.rein.id)
if descriptor is None:
raise IncompatibleProfileError(

View file

@ -64,6 +64,8 @@ class ReinAharness(Rein):
self, profile: HarnessProfile, inputs: dict[str, str], sandbox: SandboxHandle
) -> dict[str, Any]:
transport = transport_from_sandbox(sandbox)
if inputs.get("task_file") and (profile.limits.max_budget_usd is not None or profile.limits.max_turns is not None):
raise ValueError("native limits require a Glas-generated task file")
target_repo = transport.workspace
head_before = transport.git_head()
generated_task_file = not bool(inputs.get("task_file"))
@ -74,6 +76,8 @@ class ReinAharness(Rein):
"target_repo": target_repo,
"agent": inputs.get("agent", "coach"),
"timeout_seconds": profile.limits.timeout_seconds or 600,
"max_budget_usd": profile.limits.max_budget_usd,
"max_turns": profile.limits.max_turns,
}
)
@ -116,6 +120,7 @@ class ReinAharness(Rein):
events=events,
events_completeness="complete" if self.stream_tool_events else "unavailable",
tokens_spent=last_result.get("tokens_spent"),
cost_usd=last_result.get("cost_usd"),
duration_s=last_result.get("execution_time_s"),
resolved_model=last_result.get("model") or self.model,
metadata={
@ -160,6 +165,7 @@ class ReinAharness(Rein):
outcome=outcome,
reason=reason,
tokens_spent=last_result.get("tokens_spent"),
cost_usd=last_result.get("cost_usd"),
duration_s=last_result.get("execution_time_s"),
resolved_model=last_result.get("model") or self.model,
artifacts=[head_after] if committed and head_after else [],