feat: capture worker artifacts before teardown and carry native limits
All checks were successful
ci / validate (push) Successful in 2m39s
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:
parent
44991600f3
commit
fb57c0228f
9 changed files with 159 additions and 3 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 [],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue