Project applied settings, helper history and structural headroom truthfully

This commit is contained in:
Ouroboros 2026-09-18 04:17:27 +03:00
parent 621c9754b6
commit 2114985d08
43 changed files with 1111 additions and 177 deletions

View file

@ -14,7 +14,7 @@ The manifest is the SSOT of the module→domain assignment (1:1, complete over t
| D04 | Tool execution: registry, access & typed results | 20 | 0 |
| D05 | Tool surfaces: files, code, shell, media, external | 28 | 0 |
| D06 | Review stack | 66 | 0 |
| D07 | Delegation, subagents & Claudexor | 50 | 0 |
| D07 | Delegation, subagents & Claudexor | 51 | 0 |
| D08 | Supervisor: queue, workers, events & runtime control | 46 | 0 |
| D09 | Cancellation, owner control & process custody | 13 | 0 |
| D10 | Git, update & release machinery | 28 | 0 |
@ -28,7 +28,7 @@ The manifest is the SSOT of the module→domain assignment (1:1, complete over t
| D18 | Launcher, packaging, platform & shared substrate | 14 | 0 |
| D19 | Frozen contracts (ABI) | 10 | 0 |
| D20 | Presence | 9 | 0 |
| **total** | | **550** | **0** |
| **total** | | **551** | **0** |
## Dependency direction matrix (strict, pinned)
@ -422,6 +422,7 @@ No function body (≥ 10 normalized lines) is shared verbatim across domains. Ne
- `ouroboros/nanny_pacing.py`
- `ouroboros/subagent_bootstrap.py`
- `ouroboros/subagent_dispatch_notes.py`
- `ouroboros/subagent_history.py`
- `ouroboros/subagent_messages.py`
- `ouroboros/subagent_route_health.py`
- `ouroboros/subagent_runtime.py`

View file

@ -2,7 +2,7 @@
AST-derived inventory of compatibility facades, regenerated by `python scripts/regenerate_inventories.py`. Do not edit. A facade row is any runtime module whose top-level `from <population module> import ...` statements carry the `noqa: F401` re-export marker — the codebase's declared "this binding exists for its binding, not for this module's own use" convention (reference FACADE_CONSUMERS method). Leaf domains come from `ouroboros/domains.toml`; a leaf outside the facade's domain is marked ✗ (that edge also appears in the manifest's pinned direction matrix). `tests/test_generated_inventories.py` pins byte-identity, so any re-export surface change must regenerate this file.
- facade modules: **58**; marked re-export bindings: **2357**; cross-domain facade→leaf pairs: **130**
- facade modules: **58**; marked re-export bindings: **2360**; cross-domain facade→leaf pairs: **130**
| facade | domain | bindings | leaves |
|---|---|---:|---|
@ -33,7 +33,7 @@ AST-derived inventory of compatibility facades, regenerated by `python scripts/r
| `ouroboros/review_substrate.py` | D06 | 74 | `ouroboros/_outcome_receipts.py` (1 ✗D01)<br>`ouroboros/observability.py` (3 ✗D16)<br>`ouroboros/outcomes.py` (3 ✗D01)<br>`ouroboros/provider_models.py` (1 ✗D02)<br>`ouroboros/review_dispatch.py` (8)<br>`ouroboros/review_execution.py` (15)<br>`ouroboros/review_execution_projection.py` (3)<br>`ouroboros/review_projection.py` (8)<br>`ouroboros/review_records.py` (8)<br>`ouroboros/review_verdict.py` (20)<br>`ouroboros/reviewer_slot_config.py` (3)<br>`ouroboros/task_results.py` (1 ✗D17) |
| `ouroboros/skill_review.py` | D14 | 61 | `ouroboros/skill_review_cycles.py` (8)<br>`ouroboros/skill_review_history.py` (5)<br>`ouroboros/skill_review_output.py` (4)<br>`ouroboros/skill_review_packs.py` (8)<br>`ouroboros/skill_review_prompt.py` (9)<br>`ouroboros/skill_review_rebuttals.py` (4)<br>`ouroboros/skill_review_status.py` (8)<br>`ouroboros/tools/review_helpers.py` (8 ✗D06)<br>`ouroboros/triad_review.py` (3 ✗D06)<br>`ouroboros/utils.py` (4 ✗D18) |
| `ouroboros/subagent_dispatch_notes.py` | D07 | 4 | `ouroboros/agent_dispatch.py` (2 ✗D01)<br>`ouroboros/subagents.py` (2) |
| `ouroboros/subagents.py` | D07 | 4 | `ouroboros/subagent_route_health.py` (4) |
| `ouroboros/subagents.py` | D07 | 7 | `ouroboros/subagent_history.py` (3)<br>`ouroboros/subagent_route_health.py` (4) |
| `ouroboros/task_pacing.py` | D01 | 2 | `ouroboros/task_results.py` (2 ✗D17) |
| `ouroboros/task_results.py` | D17 | 8 | `ouroboros/task_result_schema.py` (8) |
| `ouroboros/tool_access.py` | D04 | 43 | `ouroboros/contracts/task_constraint.py` (2 ✗D19)<br>`ouroboros/tool_access_paths.py` (10)<br>`ouroboros/tool_access_roots.py` (9)<br>`ouroboros/tool_access_types.py` (15)<br>`ouroboros/tool_access_user_files.py` (5)<br>`ouroboros/tool_capabilities.py` (2) |

View file

@ -1060,6 +1060,8 @@ def _store_task_result(env: Any, task: Dict[str, Any], text: str,
**({"swarm_efficiency": swarm_efficiency} if swarm_efficiency else {}),
ts=utc_now_iso(),
)
from ouroboros.subagent_history import record_task_execution
record_task_execution(task, usage, drive_root=task.get("budget_drive_root") or env.drive_root)
except Exception as e:
log.warning("Failed to store task result: %s", e)

View file

@ -217,7 +217,13 @@ def _delegation_capability_fact() -> Optional[Dict[str, Any]]:
last_fact["applied_profile"] = str(last["applied_profile"])
if last.get("selected_subagent_id"):
last_fact["selected_subagent_id"] = str(last["selected_subagent_id"])
for key in ("outcome", "failure_code", "reset_at", "occurred_at", "observed_at"):
if key in last:
last_fact[key] = last[key]
delegation["subagent_last_delegation"] = last_fact
rows = last.get("latest_by_subagent")
if isinstance(rows, dict) and rows:
delegation["subagents_last_executions"] = list(rows.values())
if len(delegation) == 1:
return None
return delegation

View file

@ -229,12 +229,19 @@ def emit(drive_root: Any, kind: str, payload: Dict[str, Any]) -> bool:
answer.
"""
try:
written = bool(append_jsonl(event_log_path(drive_root), {"ts": utc_now_iso(), "type": kind, **payload}))
event = {"ts": utc_now_iso(), "type": kind, **payload}
written = bool(append_jsonl(event_log_path(drive_root), event))
except Exception:
log.warning("delegate custody row could not be written (%s)", kind, exc_info=True)
return False
if not written:
log.warning("delegate custody row was rejected by the event log (%s)", kind)
elif kind == START_FAILED:
from ouroboros.subagent_history import record_session_start_failure
try:
record_session_start_failure(drive_root, event)
except Exception:
log.debug("Start history unavailable", exc_info=True)
return written
def daemon_says_absent(exc: Any) -> bool:
@ -1018,6 +1025,11 @@ def settle_run(drive_root: Any, gateway: Any, custody: RunCustody, detail: Dict[
if custody.settled:
_retire_project_locked(drive_root, gateway, custody)
if custody.settled:
from ouroboros.subagent_history import record_session_execution
try:
record_session_execution(drive_root, custody, detail, observed)
except Exception:
log.debug("Session history unavailable", exc_info=True)
resolve_containment_fault(drive_root, custody, "settled_terminal")
# CONSUMPTION BEFORE SETTLEMENT is a fact, not a gate; asking before staging
# now would answer "no omission" for every first settlement (the render-

View file

@ -390,6 +390,7 @@ D20 = "Presence"
"ouroboros/skill_token.py" = "D14"
"ouroboros/subagent_bootstrap.py" = "D07"
"ouroboros/subagent_dispatch_notes.py" = "D07"
"ouroboros/subagent_history.py" = "D07"
"ouroboros/subagent_messages.py" = "D07"
"ouroboros/subagent_runtime.py" = "D07"
"ouroboros/subagent_work_order.py" = "D07"

View file

@ -824,6 +824,7 @@ class SettingsMeta(SettingsNetworkMeta, total=False):
setup_contract: Dict[str, Any]
available_subagents: AvailableSubagentsSettingsMeta
policy_state: SettingsPolicyState
restart_state: Dict[str, Any]
class SettingsSaveResponse(TypedDict, total=False):
@ -831,6 +832,7 @@ class SettingsSaveResponse(TypedDict, total=False):
no_changes: bool
restart_required: bool
restart_keys: list[str]
restart_state: Dict[str, Any]
immediate_changed: bool
next_task_changed: bool
warnings: list[str]
@ -1002,6 +1004,7 @@ class LocalModelStatusResponse(TypedDict, total=False):
port: int
message: str
error: str
settings_application: Dict[str, Any]
class McpStatusResponse(TypedDict, total=False):

View file

@ -576,7 +576,8 @@ async def api_local_model_start(request: Request) -> JSONResponse:
# Download can be slow, run in thread to not block the async event loop
model_path = await asyncio.to_thread(mgr.download_model, source, filename)
mgr.start_server(model_path, port=port, n_gpu_layers=n_gpu_layers, n_ctx=n_ctx, chat_format=chat_format)
mgr.start_server(model_path, port=port, n_gpu_layers=n_gpu_layers, n_ctx=n_ctx,
chat_format=chat_format, source=source, filename=filename)
return JSONResponse({"status": "starting", "model_path": model_path})
except Exception as e:
return json_exception(e)
@ -600,7 +601,7 @@ async def api_local_model_status(request: Request) -> JSONResponse:
# on the very first poll — before the user clicks Start.
if mgr._runtime_status == "unknown" and mgr.get_status() == "offline":
await asyncio.to_thread(mgr.check_runtime)
return JSONResponse(mgr.status_dict())
return JSONResponse({**mgr.status_dict(), "settings_application": mgr.settings_application(load_settings())})
except Exception as e:
return JSONResponse({"status": "error", "error": str(e)})

View file

@ -209,6 +209,36 @@ def _build_policy_state(settings: Dict[str, Any]) -> dict:
}
def _build_restart_state(settings: Dict[str, Any]) -> dict:
"""Compare saved intent with component-owned inputs, never os.environ."""
from ouroboros.config import get_runtime_mode, normalize_runtime_mode
from ouroboros.local_model import get_manager, local_model_settings
from ouroboros.server_process import applied_restart_settings
applied = applied_restart_settings()
desired = {key: settings.get(key, _SETTINGS_DEFAULTS.get(key, ""))
for key in _RESTART_REQUIRED_KEYS if key not in local_model_settings({})}
applied["OUROBOROS_RUNTIME_MODE"] = get_runtime_mode()
desired["OUROBOROS_RUNTIME_MODE"] = normalize_runtime_mode(settings.get("OUROBOROS_RUNTIME_MODE"))
pending = []
for key, value in desired.items():
if key not in applied:
continue
actual = applied[key]
if key == "OUROBOROS_SKILLS_REPO_PATH":
value = str(pathlib.Path(str(value).strip()).expanduser()) if str(value).strip() else ""
actual = str(pathlib.Path(str(actual).strip()).expanduser()) if str(actual).strip() else ""
if str(value).strip() != str(actual).strip():
pending.append(key)
unknown = sorted(set(desired) - set(applied))
local = get_manager().settings_application(settings)
summary = f"Restart Ouroboros to apply {len(pending)} saved setting(s)." if pending else ""
if unknown:
summary += f" Application state is not reported for {len(unknown)} runtime setting(s)."
return {"restart_required": bool(pending), "restart_keys": sorted(pending),
"unknown_keys": unknown, "local_model": local, "summary": summary.strip()}
def _rehydrate_mcp_servers_payload(incoming: Any, current: Any) -> list:
if not isinstance(incoming, list):
return []
@ -246,17 +276,6 @@ from ouroboros.settings_scales import (
)
def _classify_settings_changes(
old: Dict[str, Any],
new: Dict[str, Any],
) -> list:
"""Return changed keys requiring process restart; others hot-reload next task."""
return [
k for k in _RESTART_REQUIRED_KEYS
if str(new.get(k, "") or "") != str(old.get(k, "") or "")
]
def _effect_buckets(all_changed: list) -> tuple:
"""Split changed keys into the honest effect buckets for the save response.
@ -1003,6 +1022,7 @@ async def api_settings_get(request: Request) -> JSONResponse:
# not a second policy store.
try:
meta["policy_state"] = _build_policy_state(settings)
meta["restart_state"] = _build_restart_state(settings)
except Exception:
# A settings read must stay available even if an optional projection
# helper is unavailable during startup. The persisted values remain
@ -1370,10 +1390,8 @@ def _api_settings_post_locked(request: Request, body: Any) -> JSONResponse:
k for k in current
if str(current.get(k, "") or "") != str(old_effective_settings.get(k, "") or "")
]
restart_keys = _classify_settings_changes(old_effective_settings, current)
if runtime_changed:
all_changed.append("OUROBOROS_RUNTIME_MODE")
restart_keys.append("OUROBOROS_RUNTIME_MODE")
# Snapshot BEFORE the save lands: only a task already started at that
# moment keeps the previous configuration. Measuring after the write
@ -1483,9 +1501,11 @@ def _api_settings_post_locked(request: Request, body: Any) -> JSONResponse:
resp["agent_task_running"] = True
if not all_changed:
resp["no_changes"] = True
if restart_keys:
restart_state = _build_restart_state(settings_to_save)
resp["restart_state"] = restart_state
if restart_state["restart_required"]:
resp["restart_required"] = True
resp["restart_keys"] = restart_keys
resp["restart_keys"] = restart_state["restart_keys"]
if immediate_changed:
resp["immediate_changed"] = True
if next_task_changed:

View file

@ -20,6 +20,19 @@ log = logging.getLogger(__name__)
_LOCAL_MODEL_DEFAULT_PORT = 8766
def local_model_settings(values: dict) -> dict:
"""Effective launch values, shared by execution and saved/applied comparison."""
context = int(values.get("LOCAL_MODEL_CONTEXT_LENGTH", 16384))
return {
"LOCAL_MODEL_SOURCE": str(values.get("LOCAL_MODEL_SOURCE") or "").strip(),
"LOCAL_MODEL_FILENAME": str(values.get("LOCAL_MODEL_FILENAME") or "").strip(),
"LOCAL_MODEL_PORT": int(values.get("LOCAL_MODEL_PORT", _LOCAL_MODEL_DEFAULT_PORT)),
"LOCAL_MODEL_N_GPU_LAYERS": int(values.get("LOCAL_MODEL_N_GPU_LAYERS", 0)),
"LOCAL_MODEL_CONTEXT_LENGTH": context if context > 0 else 16384,
"LOCAL_MODEL_CHAT_FORMAT": str(values.get("LOCAL_MODEL_CHAT_FORMAT") or "").strip(),
}
def _get_install_command() -> list:
"""Return the llama-cpp-python pip install command."""
from ouroboros.platform_layer import pip_install_target_args
@ -82,6 +95,8 @@ class LocalModelManager:
self._serving_context_length: int = 0
self._measurement_route: bool = False
self._model_name: str = ""
self._launch_settings: dict = {}
self._applied_settings: dict = {}
self._download_progress: float = 0.0
self._stderr_buf: bytes = b""
# Runtime (llama-cpp-python) install state
@ -119,6 +134,25 @@ class LocalModelManager:
"runtime_install_log": self._runtime_install_log[-500:] if self._runtime_install_log else "",
}
def settings_application(self, settings: dict) -> dict:
"""A ready owned process applies its captured inputs; other states do not."""
desired = local_model_settings(settings)
with self._lock:
status = self.get_status()
applied = dict(self._applied_settings) if status == "ready" else {}
pending = sorted(key for key in desired if key in applied and desired[key] != applied[key])
unknown = sorted(set(desired) - set(applied)) if status == "ready" else []
if pending:
action = "Stop, then Start" if desired["LOCAL_MODEL_SOURCE"] else "Stop"
summary = f"Saved local model settings differ from the running model. Use {action} to apply them."
elif status != "ready" and desired["LOCAL_MODEL_SOURCE"]:
summary = "Local model settings are not applied to a ready model. Use the local model controls to start it."
elif unknown:
summary = "Some running local model settings were not reported."
else:
summary = ""
return {"status": status, "pending_keys": pending, "unknown_keys": unknown, "summary": summary}
def check_runtime(self) -> bool:
"""Check llama-cpp-python importability and update runtime status."""
try:
@ -400,6 +434,8 @@ class LocalModelManager:
n_gpu_layers: int = -1,
n_ctx: int = 0,
chat_format: str = "",
source: str | None = None,
filename: str | None = None,
) -> None:
"""Start the server; rechecks runtime as a safety net before Popen."""
with self._lock:
@ -410,6 +446,15 @@ class LocalModelManager:
self._port = port
self._status = "loading"
self._error = None
launch = local_model_settings({"LOCAL_MODEL_SOURCE": source, "LOCAL_MODEL_FILENAME": filename,
"LOCAL_MODEL_PORT": port, "LOCAL_MODEL_N_GPU_LAYERS": n_gpu_layers,
"LOCAL_MODEL_CONTEXT_LENGTH": n_ctx, "LOCAL_MODEL_CHAT_FORMAT": chat_format})
self._launch_settings = {key: value for key, value in launch.items()
if not ((key == "LOCAL_MODEL_SOURCE" and source is None)
or (key == "LOCAL_MODEL_FILENAME" and filename is None))}
self._applied_settings = {}
self._port = port = launch["LOCAL_MODEL_PORT"]
n_gpu_layers, chat_format = launch["LOCAL_MODEL_N_GPU_LAYERS"], launch["LOCAL_MODEL_CHAT_FORMAT"]
python = sys.executable
cmd = [
@ -420,7 +465,7 @@ class LocalModelManager:
]
if chat_format:
cmd.extend(["--chat_format", chat_format])
effective_ctx = n_ctx if n_ctx > 0 else 16384
effective_ctx = launch["LOCAL_MODEL_CONTEXT_LENGTH"]
self._context_length = effective_ctx
self._serving_context_length = effective_ctx
@ -510,6 +555,8 @@ class LocalModelManager:
proc = self._proc
start = time.time()
while time.time() - start < timeout:
if self._proc is not proc:
return
if self._proc is None or self._proc.poll() is not None:
self._status = "error"
rc = self._proc.returncode if self._proc else "?"
@ -542,6 +589,7 @@ class LocalModelManager:
self._service_binding = None
log.warning("Local model service binding unavailable; health remains ready", exc_info=True)
self._status = "ready"
self._applied_settings = dict(self._launch_settings)
self._context_length = health.get("context_length", 0)
self._model_name = health.get("model_name", "")
log.info(
@ -553,6 +601,8 @@ class LocalModelManager:
pass
time.sleep(2.0)
if self._proc is not proc:
return
self._status = "error"
self._error = f"Server failed to become healthy within {timeout}s"
log.error(self._error)
@ -569,6 +619,7 @@ class LocalModelManager:
self._proc = None
self._install_proc = None
self._status = "offline"
self._applied_settings = {}
self._error = None
self._context_length = 0

View file

@ -44,6 +44,7 @@ def auto_start_local_model(settings: dict) -> None:
n_gpu_layers=n_gpu_layers,
n_ctx=n_ctx,
chat_format=chat_format,
source=source, filename=filename,
)
log.info("Local model auto-started successfully")
except Exception as exc:

View file

@ -288,6 +288,7 @@ def _record_and_emit_empty_response(
"_last_llm_error": _short_error_text(log_msg), "execution_status": status,
"reason_code": reason, "_last_llm_error_kind": kind,
})
accumulated_usage.get("_last_llm_call_meta", {}).update(failure_code=kind)
return event_type, is_provider_glitch, permanent_body_error
@ -827,7 +828,7 @@ def _remember_llm_call(
reported_model: Any = None,
use_local: Optional[bool] = None,
) -> Dict[str, Any]:
call_meta = {
call_meta = {"ts": utc_now_iso(),
"llm_call_id": llm_call_id,
"execution_id": execution_id,
"round_id": round_id,
@ -943,10 +944,8 @@ def _record_llm_call_error(
"route": dict(getattr(error, "route", {}) or {}), "outcome": "unknown",
"request_ref": ctx.request_ref.get("manifest_ref") if ctx.request_ref else None,
}
# The caller chooses this existing repeat rail. Ordinary managed tasks
# set it to zero: their transport episode requires upstream recovery
# before a marked NEW attempt. Other callers retain their bounded rail;
# grant before logging, and uncount a grant refused before dispatch.
# Ordinary managed tasks require upstream recovery before a new attempt;
# other callers retain their bounded repeat rail.
if (
is_retryable_transport_death(error)
and repeats < ctx.transport_death_retries and ctx.attempt < ctx.transient_budget - 1
@ -973,10 +972,7 @@ def _record_llm_call_error(
"llm_call_id": ctx.llm_call_id, "round": ctx.round_idx, "attempt": ctx.attempt + 1,
"model": ctx.model,
}
# ONE error row (#355): a successful append's registered sink owns live
# delivery. Without that path, send the SAME evidence through the queue,
# preserving its identity for live/backfill dedupe. No llm_round_error
# sibling here; Background Consciousness keeps its own separate producer.
# The append sink owns delivery; queue fallback preserves the same identity.
error_event = {
"ts": utc_now_iso(), "type": "llm_api_error", **identity, "error": display_error,
"error_kind": classification.kind, "retry_same_request": will_retry,
@ -988,11 +984,15 @@ def _record_llm_call_error(
}
if not append_jsonl(ctx.drive_logs / "events.jsonl", error_event) or not has_log_sink():
emit_log_event(ctx.event_queue, error_event, log_label="LLM call error")
ctx.accumulated_usage.setdefault("llm_call_refs", []).append({
**{key: error_event[key] for key in ("ts", "llm_call_id", "model")},
"failure_code": classification.kind, "reset_at": classification.reset_at,
})
ctx.accumulated_usage.update(_last_llm_error=_short_error_text(display_error),
_last_llm_error_kind=classification.kind, _last_llm_retry_same_request=will_retry)
if classification.retry_after_sec is not None:
ctx.accumulated_usage["_last_llm_retry_after_sec"] = classification.retry_after_sec
ctx.accumulated_usage["_last_llm_reset_at"] = classification.reset_at
ctx.accumulated_usage.update(_last_llm_retry_after_sec=classification.retry_after_sec,
_last_llm_reset_at=classification.reset_at)
else:
ctx.accumulated_usage.pop("_last_llm_retry_after_sec", None)
ctx.accumulated_usage.pop("_last_llm_reset_at", None)

View file

@ -750,10 +750,11 @@ def _validate_manifest_candidate(
*,
manifest_path: str,
include_staged: bool,
inventory: SizeRatchetInventory | None = None,
) -> list[str]:
"""Shared exactness + merge-aware transition core for live and in-memory candidates."""
current = parse_size_ratchet_manifest(current_text)
inventory = collect_size_ratchet_inventory(root)
inventory = inventory if inventory is not None else collect_size_ratchet_inventory(root)
errors = _manifest_inventory_errors(current, inventory)
previous_text = resolve_committed_manifest_text(root, manifest_path=manifest_path)
@ -794,6 +795,7 @@ def validate_size_ratchet(
repo_dir: pathlib.Path,
*,
manifest_path: str = SIZE_RATCHET_MANIFEST_PATH,
inventory: SizeRatchetInventory | None = None,
) -> list[str]:
"""Validate live and staged candidates against the merge-aware committed authority.
@ -809,7 +811,8 @@ def validate_size_ratchet(
root = pathlib.Path(repo_dir).resolve()
current_path = root.joinpath(*pathlib.PurePosixPath(manifest_path).parts)
current_text = current_path.read_text(encoding="utf-8")
return _validate_manifest_candidate(root, current_text, manifest_path=manifest_path, include_staged=True)
return _validate_manifest_candidate(root, current_text, manifest_path=manifest_path,
include_staged=True, inventory=inventory)
def validate_size_ratchet_candidate(
@ -957,9 +960,53 @@ def _metrics_from_inventory(inventory: SizeRatchetInventory) -> Dict[str, Any]:
}
def compute_repo_complexity_metrics(repo_dir: pathlib.Path) -> Dict[str, Any]:
def compute_repo_complexity_metrics(
repo_dir: pathlib.Path, *, inventory: SizeRatchetInventory | None = None,
) -> Dict[str, Any]:
"""Compute health metrics from the same production inventory as the hard gate."""
return _metrics_from_inventory(collect_size_ratchet_inventory(repo_dir))
return _metrics_from_inventory(inventory if inventory is not None else collect_size_ratchet_inventory(repo_dir))
def size_headroom_lines(
inventory: SizeRatchetInventory, *, paths: Iterable[str] | None = None, limit: int = 5,
) -> list[str]:
"""Informational capacity from the same inventory as validation, never a gate.
Show touched paths when supplied; otherwise show the closest ordinary
boundaries before registered debt, so giant legacy files cannot hide a
nearly-full ordinary module. Bounds are presentation only and disclosed.
"""
selected = set(paths) if paths is not None else None
functions = len(inventory.functions)
lines = [f"Runtime functions: {functions}/{MAX_TOTAL_FUNCTIONS}; "
f"{MAX_TOTAL_FUNCTIONS - functions} remaining."]
modules = sorted(
(m for m in inventory.modules if selected is None or m.path in selected),
key=lambda m: (m.path in GIANT_PATHS or m.path in _CHECKED_IN_MANIFEST.byte_debt,
min((MAX_MODULE_LINES - m.line_count) / MAX_MODULE_LINES,
(MAX_MODULE_BYTES - m.utf8_bytes) / MAX_MODULE_BYTES), m.path),
)
for module in modules[:limit]:
line_note = ("registered line debt" if module.path in GIANT_PATHS
else f"{MAX_MODULE_LINES - module.line_count} remaining")
byte_note = ("registered byte debt" if module.path in _CHECKED_IN_MANIFEST.byte_debt
else f"{MAX_MODULE_BYTES - module.utf8_bytes} remaining")
lines.append(f"{module.path}: {module.line_count}/{MAX_MODULE_LINES} lines ({line_note}); "
f"{module.utf8_bytes}/{MAX_MODULE_BYTES} UTF-8 bytes ({byte_note}).")
if len(modules) > limit:
lines.append(f"{len(modules) - limit} more modules omitted; codebase_health provides the overview.")
functions_by_size = sorted(
(f for f in inventory.functions if selected is None or f.path in selected),
key=lambda f: ((f.path, f.qualname) in FUNCTION_DEBT, -f.line_count, f.path, f.qualname),
)
for function in functions_by_size[:limit]:
note = ("registered function debt" if (function.path, function.qualname) in FUNCTION_DEBT
else f"{MAX_FUNCTION_LINES - function.line_count} remaining")
lines.append(f"{function.path}:{function.line_start} {function.qualname}: "
f"{function.line_count}/{MAX_FUNCTION_LINES} lines ({note}).")
if len(functions_by_size) > limit:
lines.append(f"{len(functions_by_size) - limit} more functions omitted.")
return lines
def compute_complexity_metrics(sections: List[Tuple[str, str]]) -> Dict[str, Any]:

View file

@ -73,7 +73,7 @@ def bound_service_socket(drive_root: pathlib.Path, service: str, host: str, port
The existing port selector chooses the port. Uvicorn accepts this socket on
Linux, macOS and Windows; no second probe/rebind race or process authority.
"""
from ouroboros.server_process import clear_service_binding, record_service_binding
from ouroboros.server_process import clear_service_binding, record_service_binding, record_applied_restart_settings
family = socket.AF_INET6 if ":" in host else socket.AF_INET
sock = socket.socket(family, socket.SOCK_STREAM)
@ -82,6 +82,11 @@ def bound_service_socket(drive_root: pathlib.Path, service: str, host: str, port
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port))
address = sock.getsockname()
if service in {"main", "host_service"}:
record_applied_restart_settings({
"OUROBOROS_SERVER_HOST" if service == "main" else "OUROBOROS_HOST_SERVICE_PORT":
host if service == "main" else address[1],
})
try:
binding = record_service_binding(drive_root, service, address[0], address[1], pid=os.getpid())
except Exception:

View file

@ -38,6 +38,26 @@ _supervisor_stop = threading.Event()
# re-exec needs to decide whether the runtime-mode ratchet pin rides along.
_owner_restart_requested = threading.Event()
# Confirmed inputs of the components this process started. Settings saves may
# replace os.environ, so it is not an applied-state baseline. Never persisted.
_applied_restart_settings: dict = {}
_applied_settings_lock = threading.Lock()
def record_applied_restart_settings(values: dict) -> None:
"""Publish only known startup inputs, after their component starts."""
from ouroboros.settings_scales import RESTART_REQUIRED_SETTINGS
with _applied_settings_lock:
_applied_restart_settings.update({key: value for key, value in values.items()
if key in RESTART_REQUIRED_SETTINGS})
def applied_restart_settings() -> dict:
"""Return process facts without deriving them from mutable saved intent."""
with _applied_settings_lock:
return dict(_applied_restart_settings)
def _request_restart_exit(owner: bool = False) -> None:
"""Signal server shutdown with restart exit code.

View file

@ -227,7 +227,6 @@ BAND_PATHS = {
"web/modules/chat_activity.js": "Existing task activity renderer consumes the shared quota/auth wait state; no parallel task card or lifecycle.",
"web/modules/harness_accounts.js": None,
"web/modules/log_events.js": None,
"web/modules/settings.js": None,
"web/modules/skills.js": "One installed-skill page controller owns independently settling primary/optional reads and current-generation menu, identity and badge updates; domain lifecycle, cards, hub truth and shared interactions remain separate owners.",
"web/tests/chat_instance_dom.test.js": "Entered the band from 1000 lines with the alias-free subagent cost pin (stage-2 fix wave): that regression reproduces only through the real createChatInstance card path, and this file owns the DOM harness that drives it; split when the next createChatInstance face lands.",
"web/tests/harness_login_cards.test.js": "Login-card suite grew past 1000 lines with the name-the-account face cases (agy pickup, issue #232); split when the next face lands.",

View file

@ -89,6 +89,7 @@ def _record_startup_refusal(
"""Stash the typed unrun refusal for the caller's zero-spend terminal."""
from ouroboros.subagent_runtime import current_subagent_alternatives
from ouroboros.utils import utc_now_iso
snapshot = task.get("configured_subagent") if isinstance(task.get("configured_subagent"), dict) else {}
alternatives = current_subagent_alternatives(
@ -102,6 +103,7 @@ def _record_startup_refusal(
availability = dict(task.get("subagent_availability") or {}) if isinstance(
task.get("subagent_availability"), dict) else {}
availability.update({
"observed_at": utc_now_iso(),
"status": "unavailable",
"reason": str(reason or "configured_session_unavailable"),
"reset_at": str(reset_at or ""),

View file

@ -0,0 +1,190 @@
"""Dated execution disclosure in the existing subagent receipt, never routing policy."""
from __future__ import annotations
import json
import logging
import pathlib
from typing import Any, Mapping
from ouroboros.utils import utc_now_iso, write_text_atomic
log = logging.getLogger(__name__)
LAST_DELEGATION_FILENAME = "subagent_last_delegation.json"
def _last_delegation_path(drive_root=None):
from ouroboros.config import DATA_DIR
return pathlib.Path(drive_root or DATA_DIR) / "state" / LAST_DELEGATION_FILENAME
def subagent_last_delegation(drive_root=None) -> dict[str, Any]:
"""Read old single receipts and their additive per-actor history alike."""
try:
data = json.loads(_last_delegation_path(drive_root).read_text(encoding="utf-8"))
return data if isinstance(data, dict) else {}
except (OSError, ValueError):
return {}
def execution_identity(snapshot: Mapping[str, Any]) -> dict[str, str]:
"""Only execution-affecting choices, not prose, order or list fingerprints."""
route = snapshot.get("route") or {}
return {"kind": str(route.get("kind") or ""),
"target_id": str(route.get("target_id") or ""),
"credential_profile_id": str(route.get("credential_profile_id") or ""),
"effort": str(snapshot.get("effort") or ""),
"processing_preference": str(snapshot.get("processing_preference") or "")}
def record_last_delegation(*, route: str, requested_model: str, applied_model: str,
run_id: str, selected_subagent_id: str = "",
requested_profile: str = "", applied_profile: str = "",
drive_root=None, occurred_at: str = "", outcome: str = "unknown",
failure_code: str = "", reset_at: str = "",
identity: Mapping[str, Any] | None = None, task_id: str = "",
invocation_id: str = "", attempt_id: str = "", fallback=None) -> None:
"""Keep the latest fact per actor plus the old top-level receipt interface.
Occurrence and observation differ when recovery collects an old run. Replays
neither refresh its date nor displace a newer fact. The existing actor-count
bound keeps this a compact projection; the event log retains full history.
"""
from ouroboros.configured_subagents import MAX_CONFIGURED_SUBAGENTS
from ouroboros.platform_layer import acquire_exclusive_file_lock, release_exclusive_file_lock
path = _last_delegation_path(drive_root)
try:
path.parent.mkdir(parents=True, exist_ok=True)
lock_path = path.with_suffix(".lock")
lock = acquire_exclusive_file_lock(lock_path, timeout_sec=2.0, stale_sec=30.0)
if lock is None:
return
try:
old = subagent_last_delegation(drive_root)
rows = dict(old.get("latest_by_subagent") or {})
old_actor = rows.get(selected_subagent_id) or {}
previous = old_actor if old_actor.get("run_id") == run_id else old if old.get("run_id") == run_id else {}
if run_id and previous and (not occurred_at or (
previous.get("outcome") == outcome and previous.get("failure_code", "") == failure_code)):
return
observed = str(previous.get("observed_at") or utc_now_iso())
row = {"ts": occurred_at or observed, "observed_at": observed, "occurred_at": occurred_at,
"route": str(route or ""), "requested_model": str(requested_model or ""),
"applied_model": str(applied_model or ""),
"requested_profile": str(requested_profile or ""),
"applied_profile": str(applied_profile or ""),
"selected_subagent_id": str(selected_subagent_id or ""),
"run_id": str(run_id or ""), "outcome": outcome}
if identity:
row["identity"] = dict(identity)
if fallback:
row["fallback"] = dict(fallback)
for key, value in (("failure_code", failure_code), ("reset_at", reset_at),
("task_id", task_id), ("invocation_id", invocation_id), ("attempt_id", attempt_id)):
if value:
row[key] = str(value)
if selected_subagent_id and (not old_actor or (occurred_at and row["ts"] >= str(old_actor.get("ts") or ""))):
rows[selected_subagent_id] = row
rows = dict(sorted(rows.items(), key=lambda item: str(item[1].get("ts") or ""),
reverse=True)[:MAX_CONFIGURED_SUBAGENTS])
latest = row if not old or (occurred_at and row["ts"] >= str(old.get("ts") or "")) else old
write_text_atomic(path, json.dumps({**latest, "latest_by_subagent": rows}, ensure_ascii=False, indent=1))
finally:
release_exclusive_file_lock(lock_path, lock)
except Exception:
log.debug("subagent history projection write failed", exc_info=True)
def record_task_execution(task: Mapping[str, Any], usage: Mapping[str, Any], *, drive_root) -> None:
"""Project configured attempt/start facts, never task correctness."""
snapshot = task.get("configured_subagent") or {}
identity = execution_identity(snapshot)
availability = task.get("subagent_availability") or {}
if identity["kind"] == "agent_session" and availability.get("status") == "unavailable" and availability.get("reason"):
route, _, model = identity["target_id"].partition("=")
record_last_delegation(route=route, requested_model=model, applied_model="",
run_id="task:" + str(task.get("id") or ""), task_id=str(task.get("id") or ""),
selected_subagent_id=str(snapshot.get("selected_subagent_id") or ""),
requested_profile=identity["credential_profile_id"], identity=identity, drive_root=drive_root,
occurred_at=str(availability.get("observed_at") or ""), outcome="not_started",
failure_code=str(availability["reason"]), reset_at=str(availability.get("reset_at") or ""))
return
if identity["kind"] != "api_model":
return
target = identity["target_id"]
model = target[:-8].strip() if target.endswith(" (local)") else target
calls = usage.get("llm_call_refs") or []
call = next((row for row in reversed(calls) if isinstance(row, dict)
and row.get("model") == model
and (row.get("failure_code") or row.get("usable_solve_response"))), {})
if call:
failure = str(call.get("failure_code") or "")
outcome = "unknown" if failure == "provider_outcome_unknown" else "failed" if failure else "succeeded"
when = str(call.get("ts") or "")
elif availability.get("status") not in (None, "", "ready"):
failure, outcome = str(availability.get("reason") or availability["status"]), "not_started"
when = str(availability.get("observed_at") or "")
else:
return # No observation is not success or failure.
other = next((row for row in reversed(calls) if isinstance(row, dict)
and row.get("usable_solve_response") and row.get("model") != model), {}) if failure else {}
fallback = {key: other[key] for key in ("model", "llm_call_id", "ts") if key in other}
record_last_delegation(
route="api_model", requested_model=target,
applied_model=str(call.get("reported_model") or call.get("model") or "") if not failure else "",
run_id=str(call.get("llm_call_id") or task.get("id") or ""),
selected_subagent_id=str(snapshot.get("selected_subagent_id") or ""),
drive_root=drive_root, occurred_at=when, outcome=outcome,
failure_code=failure, reset_at=str(call.get("reset_at") or availability.get("reset_at") or ""),
identity=identity, task_id=str(task.get("id") or ""),
attempt_id=str(call.get("llm_call_id") or ""), fallback=fallback)
def record_session_execution(drive_root, custody, detail: Mapping[str, Any], observed: Mapping[str, Any]) -> None:
"""Common foreground/recovery settlement projection; reviewers keep their own history."""
if custody.review_owned:
return
from ouroboros.delegate_custody import invocation_record, summary_of
invocation = invocation_record(drive_root, custody.invocation_id) or {}
request = invocation.get("request") or {}
summary = summary_of(detail)
failure = summary.get("failure") if isinstance(summary.get("failure"), dict) else {}
identity = {"kind": "agent_session",
"target_id": custody.route_id + ("=" + custody.model if custody.model else ""),
"credential_profile_id": custody.profile_id,
"effort": str(request.get("effort") or ""),
"processing_preference": str((invocation.get("processing") or {}).get("requested") or "")}
record_last_delegation(
route=custody.route_id, requested_model=custody.model,
applied_model=str(observed.get("model") or ""), run_id=custody.run_id,
selected_subagent_id=custody.selected_subagent_id,
requested_profile=custody.profile_id, applied_profile=str(observed.get("profile_id") or ""),
drive_root=drive_root, occurred_at=str(summary.get("finishedAt") or ""),
outcome=str(summary.get("state") or "unknown"),
failure_code=str(failure.get("code") or ""), reset_at=str(failure.get("resetsAt") or ""), identity=identity,
task_id=custody.task_id, invocation_id=custody.invocation_id,
attempt_id=str(observed.get("attempt_id") or ""))
def record_session_start_failure(drive_root, event: Mapping[str, Any]) -> None:
from ouroboros.delegate_custody import invocation_record
invocation = invocation_record(drive_root, str(event.get("invocation_id") or "")) or {}
actor = str(invocation.get("selected_subagent_id") or "")
if not actor:
return
request = invocation.get("request") or {}
route, model = str(invocation.get("route") or ""), str(request.get("model") or "")
pin = str(request.get("credentialProfileId") or "")
record_last_delegation(
route=route, requested_model=model, applied_model="", run_id=str(event.get("invocation_id") or ""),
selected_subagent_id=actor, requested_profile=pin, drive_root=drive_root,
task_id=str(invocation.get("task_id") or ""), invocation_id=str(event.get("invocation_id") or ""),
occurred_at=str(event.get("ts") or ""), outcome="not_started" if event.get("definite") else "unknown",
failure_code=str(event.get("reason") or ""), identity={
"kind": "agent_session", "target_id": route + ("=" + model if model else ""),
"credential_profile_id": pin, "effort": str(request.get("effort") or ""),
"processing_preference": str((invocation.get("processing") or {}).get("requested") or "")})

View file

@ -274,68 +274,9 @@ def get_subagent_harness() -> DelegationRoute | None:
# only — nothing routes off it, and absence is shown as absence.
# ---------------------------------------------------------------------------
LAST_DELEGATION_FILENAME = "subagent_last_delegation.json"
def _last_delegation_path():
import pathlib
from ouroboros.config import DATA_DIR
return pathlib.Path(DATA_DIR) / "state" / LAST_DELEGATION_FILENAME
def record_last_delegation(*, route: str, requested_model: str,
applied_model: str, run_id: str,
selected_subagent_id: str = "",
requested_profile: str = "",
applied_profile: str = "") -> None:
"""Record the last delegated run's route + requested/applied model + account.
Best-effort and atomic, in the CANONICAL data plane beside the saved
settings (the reviewer-slot projection's own rule): this is UI state, not
per-task forensics — those live in the custody event log and the ledger.
``applied_model`` and ``applied_profile`` come from the same final attempt
in the engine's telemetry, '' when that attempt disclosed no such fact.
Neither the requested model nor a prior attempt supplies missing evidence;
``requested_profile`` is the pin the request carried ('' = rotation) — the
two stay separate so a requested-vs-ran mismatch is disclosable, never
rewritten.
"""
import json
from ouroboros.utils import utc_now_iso, write_text_atomic
try:
path = _last_delegation_path()
path.parent.mkdir(parents=True, exist_ok=True)
# Idempotent per run: a re-read of an ALREADY-terminal run must not
# re-stamp `ts`, or the "N ago" line would call an old run fresh.
if subagent_last_delegation().get("run_id") == str(run_id or ""):
return
write_text_atomic(path, json.dumps({
"ts": utc_now_iso(),
"route": str(route or ""),
"requested_model": str(requested_model or ""),
"applied_model": str(applied_model or ""),
"requested_profile": str(requested_profile or ""),
"applied_profile": str(applied_profile or ""),
"selected_subagent_id": str(selected_subagent_id or ""),
"run_id": str(run_id or ""),
}, ensure_ascii=False, indent=1))
except Exception:
log.debug("subagent last-delegation projection write failed", exc_info=True)
def subagent_last_delegation() -> Dict[str, Any]:
"""Read the projection ({} on any read problem — disclosure only)."""
import json
try:
data = json.loads(_last_delegation_path().read_text(encoding="utf-8"))
return data if isinstance(data, dict) else {}
except (OSError, ValueError):
return {}
from ouroboros.subagent_history import ( # noqa: F401
LAST_DELEGATION_FILENAME, record_last_delegation, subagent_last_delegation,
)
@dataclass(frozen=True)

View file

@ -966,7 +966,10 @@ def _advisory_pre_sdk_gate(
state = load_state(drive_root)
# Readiness gate first: reject clean worktree before fresh-run shortcut.
readiness_warnings = check_worktree_readiness(repo_dir, paths=paths)
readiness_information: List[str] = []
readiness_warnings = check_worktree_readiness(repo_dir, paths=paths, information=readiness_information)
if readiness_information:
ctx.emit_progress_fn("Size headroom (information):\n" + "\n".join(readiness_information))
if readiness_warnings and any("no uncommitted changes" in w.lower() for w in readiness_warnings):
ctx.emit_progress_fn(f"⚠️ Advisory readiness gate: {'; '.join(readiness_warnings)}")
return readiness_warnings, "", _json_response({

View file

@ -972,7 +972,6 @@ def _delegate_wait(ctx: ToolContext, run_id: str, wait_sec: Optional[int] = None
if breach:
return _halt_breached_run(ctx, gateway, entry, breach)
if state in _TERMINAL_STATES:
was_settled = bool(entry.settled)
settlement = custody.settle_run(custody.custody_root(ctx), gateway, entry, detail)
payload = _delivered_terminal_payload(ctx, rid, detail, authority, entry, gateway)
payload["settlement"] = settlement
@ -984,23 +983,6 @@ def _delegate_wait(ctx: ToolContext, run_id: str, wait_sec: Optional[int] = None
{"gateway": gateway} if entry.resource_ref.get("workspace_kind") == "directory" else {}))
if capture is not None:
payload["workspace_capture"] = capture
# The «last delegated run» settings receipt (Subagents section):
# requested vs applied model, written ONLY when THIS call performed
# a SUCCESSFUL settlement — a later wait re-reading an already-settled
# run must not re-date it (or replace a newer run as "last"), and a
# settlement whose durable obligations failed must not mint a receipt
# it would re-mint on every retry. The delegated REVIEW sessions never
# pass here — they have their own receipt store
# (reviewer_slot_last_execution.json).
if not was_settled and bool(settlement.get("settled")):
from ouroboros.subagents import record_last_delegation
record_last_delegation(
route=entry.route_id, requested_model=entry.model,
applied_model=str(payload.get("model") or ""), run_id=rid,
selected_subagent_id=entry.selected_subagent_id,
# Applied = the same final attempt as the model; requested replays off STARTED.
requested_profile=entry.profile_id,
applied_profile=str((payload.get("observed_attempt") or {}).get("profile_id") or ""))
# D7 made load-bearing: settlement is where "paid for and never read"
# becomes permanent, so the parent is told in WORDS here — not left to
# infer it from `output_delivery.consumed`. Re-settling an already

View file

@ -13,10 +13,11 @@ log = logging.getLogger(__name__)
def _codebase_health(ctx: ToolContext) -> str:
"""Compute and format codebase health report."""
try:
from ouroboros.review import compute_repo_complexity_metrics
from ouroboros.review import collect_size_ratchet_inventory, compute_repo_complexity_metrics, size_headroom_lines
repo_dir = pathlib.Path(ctx.repo_dir)
metrics = compute_repo_complexity_metrics(repo_dir)
inventory = collect_size_ratchet_inventory(repo_dir)
metrics = compute_repo_complexity_metrics(repo_dir, inventory=inventory)
stats = {
"files": metrics["total_files"],
"chars": metrics["total_bytes"],
@ -42,6 +43,8 @@ def _codebase_health(ctx: ToolContext) -> str:
lines.append(f"**Functions:** {metrics['total_functions']}")
lines.append(f"**Avg function length:** {metrics['avg_function_length']} lines")
lines.append(f"**Max function length:** {metrics['max_function_length']} lines")
lines.append("\n### Size Headroom (information; official CI enforces the limits)")
lines.extend(size_headroom_lines(inventory))
from ouroboros.review import (
MAX_FUNCTION_LINES,
@ -131,7 +134,7 @@ def _codebase_health(ctx: ToolContext) -> str:
try:
from ouroboros.review import validate_size_ratchet
ratchet_findings = validate_size_ratchet(repo_dir)
ratchet_findings = validate_size_ratchet(repo_dir, inventory=inventory)
except Exception as ratchet_exc:
lines.append(f"\n### Size-Ratchet Findings\n ⚠️ validator unavailable: {ratchet_exc}")
else:

View file

@ -695,6 +695,7 @@ def get_advisory_runtime_diagnostics(model: str, prompt_chars: int,
def check_worktree_readiness(
repo_dir: "Path",
paths: "list[str] | None" = None,
*, information: "list[str] | None" = None,
) -> "list[str]":
"""Run cheap deterministic pre-advisory checks; never crash."""
repo_dir = Path(repo_dir)
@ -772,9 +773,13 @@ def check_worktree_readiness(
# line rejects the same finding. Cheap since the history replay retired
# (one live inventory plus a couple of git object reads).
try:
from ouroboros.review import validate_size_ratchet # local import: ouroboros.review imports this module
from ouroboros.review import collect_size_ratchet_inventory, size_headroom_lines, validate_size_ratchet
for finding in validate_size_ratchet(repo_dir):
inventory = collect_size_ratchet_inventory(repo_dir) if information is not None else None
if information is not None and inventory is not None:
touched = parse_changed_paths_from_porcelain(status_result.stdout or "") if status_result is not None else []
information.extend(size_headroom_lines(inventory, paths=touched))
for finding in validate_size_ratchet(repo_dir, inventory=inventory):
warnings.append(f"official CI will enforce: {finding}")
except Exception as exc:
# A broken validator must not silently disable the only local surface —

View file

@ -685,6 +685,9 @@ def _run_supervisor(settings: dict) -> None:
restored_pending = restore_pending_from_snapshot(terminalized=interrupted_running)
kill_workers(preserve_pending=True)
spawn_workers(max_workers)
from ouroboros.server_process import record_applied_restart_settings
record_applied_restart_settings({"OUROBOROS_MAX_WORKERS": max_workers,
"OUROBOROS_SKILLS_REPO_PATH": settings.get("OUROBOROS_SKILLS_REPO_PATH", "")})
persist_queue_snapshot(reason="startup")
try:
from ouroboros.delegate_recovery import pre_adopt_planned_handoffs
@ -877,17 +880,13 @@ def _run_supervisor(settings: dict) -> None:
except Exception as exc:
if _supervisor_stop.is_set() or _restart_requested.is_set():
# The shutdown/restart tore the bus down under this tick (a
# Manager proxy raising BrokenPipe/EOF): not a crash, no alarm.
# A shutdown-torn Manager proxy is not a supervisor crash.
log.info("Supervisor loop exiting on shutdown: %s", exc)
break
crash_count += 1
log.error("Supervisor loop crash #%d: %s", crash_count, exc, exc_info=True)
if crash_count >= 3:
# Visible death: previously the loop returned with
# _supervisor_ready still set and no _supervisor_error, so
# tasks silently stopped being assigned with a healthy-looking
# /api/state. Record the failure and tell the owner.
# Clear readiness and notify: a dead loop must not look healthy.
_supervisor_error = f"Supervisor loop died after 3 consecutive crashes: {exc}"
_supervisor_ready.clear()
log.critical("Supervisor exceeded max retries: %s", _supervisor_error)

View file

@ -194,7 +194,7 @@ def test_d29_absent_authroute_records_empty_never_invented(tmp_path, monkeypatch
def test_final_attempt_identity_survives_settlement_and_parent_delivery(tmp_path, monkeypatch, observed):
from ouroboros.subagents import subagent_last_delegation
monkeypatch.setattr("ouroboros.config.DATA_DIR", tmp_path / "canonical-data")
monkeypatch.setattr("ouroboros.config.DATA_DIR", tmp_path)
payload, ledger, event = _settled_run(tmp_path, monkeypatch, {
"state": "succeeded", "spendUsd": 0.0, "model": "request-echo",
"authRoute": {"profileId": "old-profile"}, "effectiveAccess": "readonly",
@ -263,7 +263,7 @@ def _settled_run(tmp_path, monkeypatch, summary, observed=None):
monkeypatch.setattr(gw, "ClaudexorGateway", lambda *a, **k: _Stub())
delegate._CUSTODY.clear()
delegate._CUSTODY["run-1"] = delegate._RunCustody(
task_id="t-a", route_id="r", model="m", project_id="p", project_owned=True)
run_id="run-1", task_id="t-a", route_id="r", model="m", project_id="p", project_owned=True)
ctx = ToolContext(repo_dir=tmp_path, drive_root=tmp_path)
ctx.task_id = "t-a"
ctx.task_metadata = {"root_task_id": "t-a"}
@ -298,7 +298,7 @@ def _waited_run(tmp_path, monkeypatch, summary, requested_model="m", observed=No
monkeypatch.setattr(gw, "ClaudexorGateway", lambda *a, **k: _Stub())
delegate._CUSTODY.clear()
delegate._CUSTODY["run-1"] = delegate._RunCustody(
task_id="t-a", route_id="r", model=requested_model,
run_id="run-1", task_id="t-a", route_id="r", model=requested_model,
project_id="p", project_owned=False)
ctx = ToolContext(repo_dir=tmp_path, drive_root=tmp_path)
ctx.task_id = "t-a"
@ -352,7 +352,7 @@ def test_the_last_delegation_projection_is_written_at_the_settle_seam(tmp_path,
# Isolated data plane: the projection is keyed off config.DATA_DIR, which
# xdist workers would otherwise share (and the sibling test writes it too).
monkeypatch.setattr("ouroboros.config.DATA_DIR", tmp_path / "proj-data")
monkeypatch.setattr("ouroboros.config.DATA_DIR", tmp_path / "proj")
_waited_run(tmp_path / "proj", monkeypatch,
{"state": "succeeded", "spendUsd": 0.0, "model": "claude-opus-5"},
requested_model="sonnet")
@ -386,29 +386,24 @@ def test_no_receipt_on_a_failed_settlement_and_one_after_the_successful_retry(tm
import ouroboros.delegate_custody as custody_mod
from ouroboros.subagents import subagent_last_delegation
monkeypatch.setattr("ouroboros.config.DATA_DIR", tmp_path / "receipt-data")
monkeypatch.setattr("ouroboros.config.DATA_DIR", tmp_path / "w1")
real_settle = custody_mod.settle_run
real_emit = custody_mod.emit
outcomes = iter([False, True])
def _flaky_settle(drive_root, gateway, custody, detail):
ok = next(outcomes)
result = real_settle(drive_root, gateway, custody, detail)
if not ok:
custody.settled = False
result = dict(result)
result["settled"] = False
return result
def flaky_emit(drive_root, kind, payload):
if kind == custody_mod.SETTLED and not next(outcomes):
return False
return real_emit(drive_root, kind, payload)
monkeypatch.setattr(custody_mod, "settle_run", _flaky_settle)
monkeypatch.setattr("ouroboros.tools.delegate.custody.settle_run", _flaky_settle, raising=False)
monkeypatch.setattr(custody_mod, "emit", flaky_emit)
_waited_run(tmp_path / "w1", monkeypatch,
{"state": "succeeded", "spendUsd": 0.0, "model": "claude-opus-5"},
requested_model="sonnet")
assert subagent_last_delegation() == {}, "receipt minted on a FAILED settlement"
_waited_run(tmp_path / "w2", monkeypatch,
_waited_run(tmp_path / "w1", monkeypatch,
{"state": "succeeded", "spendUsd": 0.0, "model": "claude-opus-5"},
requested_model="sonnet")
record = subagent_last_delegation()

View file

@ -114,7 +114,7 @@ def _waited_run(tmp_path, monkeypatch, summary, requested_model="m",
monkeypatch.setattr(gw, "ClaudexorGateway", lambda *a, **k: _Stub())
delegate._CUSTODY.clear()
delegate._CUSTODY["run-1"] = delegate._RunCustody(
task_id="t-a", route_id="r", model=requested_model,
run_id="run-1", task_id="t-a", route_id="r", model=requested_model,
profile_id=requested_profile, selected_subagent_id=selected_subagent_id,
project_id="p", project_owned=False)
ctx = ToolContext(repo_dir=tmp_path, drive_root=tmp_path)
@ -134,7 +134,7 @@ def test_the_receipt_carries_the_requested_and_applied_account(tmp_path, monkeyp
writes '', never the request dressed up as the applied account."""
from ouroboros.subagents import subagent_last_delegation
monkeypatch.setattr("ouroboros.config.DATA_DIR", tmp_path / "acct-data")
monkeypatch.setattr("ouroboros.config.DATA_DIR", tmp_path / "acct")
_waited_run(tmp_path / "acct", monkeypatch,
{"state": "succeeded", "spendUsd": 0.0, "model": "m",
"authRoute": {"profileId": "previous-profile"}},
@ -155,7 +155,7 @@ def test_the_receipt_carries_the_requested_and_applied_account(tmp_path, monkeyp
assert subagent_last_delegation() == record
# A summary echo cannot replace the missing final-attempt receipt.
monkeypatch.setattr("ouroboros.config.DATA_DIR", tmp_path / "acct-data-2")
monkeypatch.setattr("ouroboros.config.DATA_DIR", tmp_path / "acct2")
_waited_run(tmp_path / "acct2", monkeypatch,
{"state": "succeeded", "spendUsd": 0.0, "model": "m",
"authRoute": {"profileId": "previous-profile"}},

View file

@ -173,7 +173,7 @@ def test_gateway_contract_endpoint_index_matches_router_and_types(tmp_path):
version = (pathlib.Path(__file__).resolve().parent.parent / "VERSION").read_text(encoding="utf-8").strip()
assert f"GATEWAY_CONTRACT_VERSION = '{version}'" in text
settings_meta_fields = {
"custom_secret_keys", "setup_contract", "available_subagents", "policy_state",
"custom_secret_keys", "setup_contract", "available_subagents", "policy_state", "restart_state",
}
assert settings_meta_fields <= set(SettingsMeta.__annotations__)
assert _js_typedef_fields(text, "SettingsMeta") == settings_meta_fields

View file

@ -325,7 +325,7 @@ def test_a_pre_commit_failure_is_still_reported_as_unsaved(monkeypatch, isolated
from ouroboros.gateway import settings as settings_mod
app = _settings_app(monkeypatch, isolated_settings)
monkeypatch.setattr(settings_mod, "_classify_settings_changes",
monkeypatch.setattr(settings_mod, "_merge_settings_payload",
lambda *_a, **_k: (_ for _ in ()).throw(RuntimeError("before the write")))
resp = TestClient(app).post("/api/settings", json={"TOTAL_BUDGET": "25"})

View file

@ -29,6 +29,11 @@ def isolated_settings(tmp_path, monkeypatch):
monkeypatch.setattr(cfg, "DATA_DIR", data_dir, raising=True)
monkeypatch.setattr(cfg, "SETTINGS_PATH", settings_path, raising=True)
cfg.reset_runtime_mode_baseline_for_tests()
from ouroboros import server_process
monkeypatch.setattr(server_process, "_applied_restart_settings", {
key: cfg.SETTINGS_DEFAULTS[key] for key in (
"OUROBOROS_MAX_WORKERS", "OUROBOROS_SERVER_HOST", "OUROBOROS_HOST_SERVICE_PORT",
"OUROBOROS_SKILLS_REPO_PATH")})
yield settings_path
cfg.reset_runtime_mode_baseline_for_tests()

View file

@ -0,0 +1,173 @@
"""Saved settings are compared with actual component startup/health facts."""
import asyncio
import json
import subprocess
import sys
import threading
from types import SimpleNamespace
import pytest
from starlette.requests import Request
from ouroboros import config, local_model, server_process
from ouroboros.gateway import models, settings
from ouroboros.server_entrypoint import bound_service_socket
@pytest.fixture
def applied(monkeypatch):
monkeypatch.setattr(server_process, "_applied_restart_settings", {})
monkeypatch.setattr(config, "get_runtime_mode", lambda: "advanced")
manager = local_model.LocalModelManager()
monkeypatch.setattr(local_model, "_manager", manager)
saved = dict(config.SETTINGS_DEFAULTS)
server_process.record_applied_restart_settings({key: saved[key] for key in (
"OUROBOROS_MAX_WORKERS", "OUROBOROS_SERVER_HOST", "OUROBOROS_HOST_SERVICE_PORT",
"OUROBOROS_SKILLS_REPO_PATH",
)})
return saved, manager
def test_server_pending_truth_survives_reload_revert_and_environment_save(applied, monkeypatch):
saved, _manager = applied
for key, value in {
"OUROBOROS_RUNTIME_MODE": "pro", "OUROBOROS_MAX_WORKERS": 19,
"OUROBOROS_SERVER_HOST": "0.0.0.0", "OUROBOROS_HOST_SERVICE_PORT": 9123,
"OUROBOROS_SKILLS_REPO_PATH": "/example/skills",
}.items():
changed = {**saved, key: value}
monkeypatch.setenv(key, str(value)) # a Save is not application
state = settings._build_restart_state(changed)
assert state["restart_keys"] == [key]
assert settings._build_restart_state({**changed, "TOTAL_BUDGET": 300}) == state
assert settings._build_restart_state(saved)["restart_required"] is False
def test_missing_component_facts_stay_unknown(applied, monkeypatch):
saved, _manager = applied
monkeypatch.setattr(server_process, "_applied_restart_settings", {})
state = settings._build_restart_state(saved)
assert len(state["unknown_keys"]) == 4
assert "not reported" in state["summary"]
@pytest.mark.serial
def test_socket_owners_publish_only_successfully_bound_inputs(tmp_path, monkeypatch):
monkeypatch.setattr(server_process, "_applied_restart_settings", {})
with bound_service_socket(tmp_path, "host_service", "127.0.0.1", 0) as sock:
port = sock.getsockname()[1]
assert server_process.applied_restart_settings()["OUROBOROS_HOST_SERVICE_PORT"] == port
with pytest.raises(OSError):
with bound_service_socket(tmp_path, "main", "127.0.0.1", port):
pass
assert "OUROBOROS_SERVER_HOST" not in server_process.applied_restart_settings()
def test_every_local_setting_is_compared_only_after_confirmed_health(applied):
saved, manager = applied
saved.update(LOCAL_MODEL_SOURCE="fixture/model", LOCAL_MODEL_FILENAME="fixture.gguf")
manager._launch_settings = local_model.local_model_settings(saved)
manager._status = "loading"
assert manager.settings_application(saved)["pending_keys"] == []
assert "not applied" in manager.settings_application(saved)["summary"]
manager._status = "ready"
manager._applied_settings = dict(manager._launch_settings)
for key, value in {
"LOCAL_MODEL_SOURCE": "another/model", "LOCAL_MODEL_FILENAME": "other.gguf",
"LOCAL_MODEL_PORT": 9345, "LOCAL_MODEL_N_GPU_LAYERS": 17,
"LOCAL_MODEL_CONTEXT_LENGTH": 32768, "LOCAL_MODEL_CHAT_FORMAT": "other-format",
}.items():
state = settings._build_restart_state({**saved, key: value})
assert state["restart_required"] is False
assert state["local_model"]["pending_keys"] == [key]
assert manager.settings_application({**saved, "LOCAL_MODEL_CONTEXT_LENGTH": 0})["pending_keys"] == []
manager._status = "error"
assert "not applied" in manager.settings_application(saved)["summary"]
assert manager.settings_application(saved)["status"] == "error"
def _request(body):
async def receive():
return {"type": "http.request", "body": json.dumps(body).encode(), "more_body": False}
return Request({"type": "http", "method": "POST", "path": "/api/local-model/start",
"headers": [], "app": SimpleNamespace(state=SimpleNamespace())}, receive)
@pytest.fixture
def healthy_model_process(tmp_path, monkeypatch):
"""The real manager starts a lightweight /v1/models child, never downloads a model."""
manager = local_model.LocalModelManager()
monkeypatch.setattr(local_model, "_manager", manager)
monkeypatch.setattr(config, "DATA_DIR", tmp_path / "data")
script = tmp_path / "model_server.py"
script.write_text(
'import http.server,json,sys\n'
'class Handler(http.server.BaseHTTPRequestHandler):\n'
' def do_GET(self):\n'
' body=json.dumps({"data":[{"id":"fixture","context_window":9999}]}).encode()\n'
' self.send_response(200);self.send_header("Content-Length",str(len(body)));self.end_headers();self.wfile.write(body)\n'
' def log_message(self,*args):pass\n'
'http.server.HTTPServer(("127.0.0.1",int(sys.argv[1])),Handler).serve_forever()\n', encoding="utf-8")
original_popen, original_run = subprocess.Popen, subprocess.run
commands = []
def popen(command, **kwargs):
if "ouroboros.local_model_server" in command:
commands.append(list(command))
command = [sys.executable, str(script), str(command[command.index("--port") + 1])]
return original_popen(command, **kwargs)
def run(command, **kwargs):
if command[-1] == "import llama_cpp":
return subprocess.CompletedProcess(command, 0, "", "")
return original_run(command, **kwargs)
settled = threading.Event()
original_health = manager._wait_for_healthy
def health():
try:
original_health(timeout=8)
finally:
settled.set()
monkeypatch.setattr(local_model.subprocess, "Popen", popen)
monkeypatch.setattr(local_model.subprocess, "run", run)
monkeypatch.setattr(manager, "_wait_for_healthy", health)
monkeypatch.setattr(manager, "download_model", lambda source, filename: str(tmp_path / filename))
try:
yield manager, settled, commands
finally:
manager.stop_server()
@pytest.mark.serial
def test_actual_local_start_health_and_restart_clear_saved_mismatch(healthy_model_process, monkeypatch, free_tcp_port):
manager, settled, commands = healthy_model_process
saved = {**config.SETTINGS_DEFAULTS, "LOCAL_MODEL_SOURCE": "fixture/model",
"LOCAL_MODEL_FILENAME": "fixture.gguf", "LOCAL_MODEL_PORT": free_tcp_port,
"LOCAL_MODEL_CONTEXT_LENGTH": 0, "LOCAL_MODEL_N_GPU_LAYERS": 3}
monkeypatch.setattr(models, "load_settings", lambda: saved)
body = {"source": "fixture/model", "filename": "fixture.gguf", "port": free_tcp_port,
"n_ctx": 0, "n_gpu_layers": 3, "chat_format": ""}
response = asyncio.run(models.api_local_model_start(_request(body)))
assert response.status_code == 200
assert settled.wait(10)
assert manager.is_running, manager.status_dict()
assert commands[-1][commands[-1].index("--n_ctx") + 1] == "16384"
assert manager.settings_application(saved)["pending_keys"] == []
saved["LOCAL_MODEL_CONTEXT_LENGTH"] = 8192
assert manager.settings_application(saved)["pending_keys"] == ["LOCAL_MODEL_CONTEXT_LENGTH"]
asyncio.run(models.api_local_model_stop(_request({})))
assert manager.settings_application(saved)["status"] == "offline"
settled.clear()
response = asyncio.run(models.api_local_model_start(_request({**body, "n_ctx": 8192})))
assert response.status_code == 200
assert settled.wait(10)
status = json.loads(asyncio.run(models.api_local_model_status(_request({}))).body)
assert status["status"] == "ready"
assert status["settings_application"]["pending_keys"] == []
# Provider training metadata does not replace the effective launch window.
assert status["context_length"] == 9999
assert manager._applied_settings["LOCAL_MODEL_CONTEXT_LENGTH"] == 8192

View file

@ -0,0 +1,168 @@
"""Production Settings against an isolated server and a tiny local-model HTTP child."""
import os
import json
import pathlib
import sys
import textwrap
import pytest
from tests import test_ui_smoke_playwright as smoke
from tests.ui_chat_viewport_smoke import _CAPTURE_TEST_SOCKET, _SETTLE_RESTORE_FRAMES
pytest_plugins = ("tests.test_ui_smoke_playwright",)
@pytest.fixture
def settings_server(request, tmp_path, monkeypatch):
if os.name == "nt":
pytest.skip("POSIX test launcher; production browser behavior is shared")
bootstrap = tmp_path / "bootstrap.py"
fake_model = tmp_path / "fake_model.py"
fake_model.write_text(textwrap.dedent('''\
import http.server, json, sys
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
body = json.dumps({"data": [{"id": "fixture", "context_window": 9999}]}).encode()
self.send_response(200)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *args): pass
http.server.HTTPServer(("127.0.0.1", int(sys.argv[1])), Handler).serve_forever()
'''), encoding="utf-8")
bootstrap.write_text(textwrap.dedent(f'''\
import pathlib, runpy, subprocess, sys
sys.path.insert(0, {smoke.REPO_ROOT!r})
from ouroboros.local_model import LocalModelManager
LocalModelManager.download_model = lambda self, source, filename: str(pathlib.Path({str(tmp_path)!r}) / filename)
original_popen, original_run = subprocess.Popen, subprocess.run
class Popen(original_popen):
def __init__(self, command, **kwargs):
if isinstance(command, list) and "ouroboros.local_model_server" in command:
command = [sys.executable, {str(fake_model)!r}, str(command[command.index("--port") + 1])]
super().__init__(command, **kwargs)
def run(command, **kwargs):
if isinstance(command, list) and command[-1] == "import llama_cpp":
return subprocess.CompletedProcess(command, 0, "", "")
return original_run(command, **kwargs)
subprocess.Popen, subprocess.run = Popen, run
sys.argv = sys.argv[1:]
runpy.run_path({str(pathlib.Path(smoke.REPO_ROOT) / 'server.py')!r}, run_name="__main__")
'''), encoding="utf-8")
launcher = tmp_path / "python-fixture"
launcher.write_text(f'#!/bin/sh\nexec "{sys.executable}" "{bootstrap}" "$@"\n', encoding="utf-8")
launcher.chmod(0o700)
monkeypatch.setattr(smoke, "_fixture_python", lambda: str(launcher))
return request.getfixturevalue("direct_server_with_data")
@pytest.mark.serial
@pytest.mark.ui_browser
@pytest.mark.parametrize("engine,width", [("chromium", 1400), ("webkit", 390)])
def test_pending_survives_reconnect_draft_and_restart_request(settings_server, engine, width, free_tcp_port):
from playwright.sync_api import expect, sync_playwright
with sync_playwright() as pw:
browser = getattr(pw, engine).launch(headless=True)
page = browser.new_page(viewport={"width": width, "height": 1000})
errors = []
page.on('pageerror', lambda error: errors.append(str(error)))
page.add_init_script(f"({_CAPTURE_TEST_SOCKET})()")
page.add_init_script("""const originalSend = WebSocket.prototype.send;
WebSocket.prototype.send = function(data) {
if (JSON.parse(data).cmd === '/restart') { window.restartSent = true; return; }
return originalSend.call(this, data);
};""")
try:
def open_settings():
page.goto(settings_server["url"], wait_until="domcontentloaded")
if width < 980:
page.locator('#page-chat [data-mobile-nav-toggle]').click()
page.locator('[data-nav-page="settings"]').click()
expect(page.locator('#btn-save-settings')).to_be_enabled(timeout=30_000)
page.evaluate(_SETTLE_RESTORE_FRAMES)
def fill_value(selector, value):
page.locator(selector).evaluate("(node, value) => {node.value=value; node.dispatchEvent(new Event('input',{bubbles:true}));}", str(value))
def save():
page.locator('#btn-save-settings').click()
page.wait_for_function("!document.querySelector('#btn-save-settings').disabled || document.querySelector('[data-confirm-ok]')")
if page.locator('[data-confirm-ok]').is_visible():
page.locator('[data-confirm-ok]').click()
expect(page.locator('#btn-save-settings')).to_be_enabled(timeout=30_000)
expect(page.locator('#settings-status')).to_contain_text('saved', timeout=30_000)
open_settings()
fill_value('#s-workers', 2)
save()
expect(page.locator('#btn-restart-now')).to_be_visible()
open_settings() # a completely new page, no local latch
expect(page.locator('#btn-restart-now')).to_be_visible()
page.locator('#btn-restart-now').click()
page.locator('[data-confirm-ok]').click()
page.wait_for_function('window.restartSent === true')
expect(page.locator('#btn-restart-now')).to_be_visible()
fill_value('#s-workers', 3) # preserve a draft over reconnect metadata
page.evaluate("window.__testSockets[0].close()")
page.wait_for_function("window.__testSockets.some(socket => socket.readyState === WebSocket.OPEN)")
expect(page.locator('#s-workers')).to_have_value('3')
expect(page.locator('#btn-restart-now')).to_be_visible()
fill_value('#s-workers', 1)
save()
expect(page.locator('#btn-restart-now')).to_be_hidden()
fill_value('#s-local-source', 'fixture/model')
fill_value('#s-local-filename', 'fixture.gguf')
fill_value('#s-local-port', free_tcp_port)
fill_value('#s-local-ctx', 0)
save()
page.locator('[data-settings-tab="advanced"]').click()
page.locator('#btn-local-start').click()
expect(page.locator('#local-model-status')).to_contain_text('Ready', timeout=30_000)
fill_value('#s-local-ctx', 8192)
save()
expect(page.locator('#settings-restart-status')).to_contain_text('Stop, then Start')
expect(page.locator('#btn-restart-now')).to_be_hidden()
evidence = pathlib.Path(os.environ.get('OUROBOROS_UI_EVIDENCE_DIR', str(settings_server['data_dir'].parent)))
evidence.mkdir(parents=True, exist_ok=True)
page.locator('#settings-restart-status').scroll_into_view_if_needed()
page.screenshot(path=str(evidence / f'settings-applied-pending-{engine}.png'))
page.locator('#btn-local-stop').click()
expect(page.locator('#local-model-status')).to_contain_text('Offline', timeout=30_000)
page.locator('#btn-local-start').click()
expect(page.locator('#local-model-status')).to_contain_text('Ready', timeout=30_000)
expect(page.locator('#settings-restart-status')).to_be_hidden(timeout=30_000)
page.screenshot(path=str(evidence / f'settings-applied-ready-{engine}.png'))
# The same actual receipt consumer on the owner-facing Agents card.
actor = {"subagent_id": "fixture-helper", "recommended_use": "Fixture helper",
"route": {"kind": "api_model", "target_id": "openai-compatible::mock-model"},
"effort": "high", "processing_preference": "standard"}
from ouroboros.subagent_history import record_last_delegation, execution_identity
record_last_delegation(route="api_model", requested_model=actor["route"]["target_id"],
applied_model="", run_id="browser-failure", selected_subagent_id="fixture-helper",
drive_root=settings_server['data_dir'], occurred_at="2026-09-18T12:00:00Z",
outcome="failed", failure_code="quota_exhausted", identity=execution_identity(actor))
response = page.request.post(settings_server['url'] + '/api/settings', data={
"OUROBOROS_SUBAGENTS": json.dumps({"enabled": True, "items": [actor]})})
assert response.ok, response.text()
open_settings()
page.locator('[data-settings-tab="agents"]').click()
meta = page.locator('[data-subagent-meta]').first
expect(meta).to_contain_text('failed (quota_exhausted)', timeout=30_000)
expect(meta).to_contain_text('2026-09-18T12:00:00Z')
assert meta.evaluate("node => getComputedStyle(node).whiteSpace") == 'normal'
assert meta.evaluate("node => node.scrollWidth <= node.clientWidth")
meta.scroll_into_view_if_needed()
page.screenshot(path=str(evidence / f'subagent-history-{engine}.png'))
assert errors == []
except Exception:
evidence = pathlib.Path(os.environ.get('OUROBOROS_UI_EVIDENCE_DIR', str(settings_server['data_dir'].parent)))
evidence.mkdir(parents=True, exist_ok=True)
page.screenshot(path=str(evidence / f'settings-failure-{engine}.png'))
print(page.locator('body').inner_text()[-4000:])
raise
finally:
browser.close()

View file

@ -0,0 +1,98 @@
"""Capacity is information at the real health/readiness consumers, not a gate."""
import subprocess
from types import SimpleNamespace
from ouroboros import review
from ouroboros.tools import health, review_helpers
def test_near_limit_module_is_not_hidden_by_registered_giants(monkeypatch):
giants = tuple(review.GatedModule(f"old{i}.py", 2000 + i, 5000) for i in range(12))
monkeypatch.setattr(review, "GIANT_PATHS", frozenset(m.path for m in giants))
inventory = review.SizeRatchetInventory(
modules=(*giants, review.GatedModule("near.py", review.MAX_MODULE_LINES - 1, 199999)),
functions=(), giant_paths=frozenset(), function_debt=frozenset(),
band_paths=frozenset(), byte_debt={},
)
lines = review.size_headroom_lines(inventory)
assert lines[1].startswith("near.py:")
assert "1 remaining" in lines[1]
assert any("registered line debt" in line for line in lines)
assert any("8 more modules omitted" in line for line in lines)
assert review.size_headroom_lines(inventory, paths=["near.py"])[1:] == [lines[1]]
def test_headroom_uses_live_limits_and_retains_zero_and_negative(monkeypatch):
monkeypatch.setattr(review, "MAX_TOTAL_FUNCTIONS", 1)
module = review.GatedModule("sample.py", review.MAX_MODULE_LINES, review.MAX_MODULE_BYTES + 1)
functions = (review.GatedFunction("sample.py", "a", 1, review.MAX_FUNCTION_LINES),
review.GatedFunction("sample.py", "b", 2, review.MAX_FUNCTION_LINES + 1))
inventory = review.SizeRatchetInventory((module,), functions, frozenset(), frozenset(), frozenset(), {})
lines = review.size_headroom_lines(inventory)
assert "2/1; -1 remaining" in lines[0]
assert "lines (0 remaining)" in lines[1]
assert "UTF-8 bytes (-1 remaining)" in lines[1]
assert "b:" in lines[2] and "-1 remaining" in lines[2]
assert "a:" in lines[3] and "0 remaining" in lines[3]
def test_headroom_counts_normalized_utf8_source(tmp_path):
source = "# Snowman: ☃\r\ndef sample():\r\n return 1\r\n"
(tmp_path / "sample.py").write_bytes(source.encode("utf-8"))
inventory = review.collect_size_ratchet_inventory(tmp_path)
line = review.size_headroom_lines(inventory)[1]
assert "sample.py: 3/" in line
assert f"{len(source.replace(chr(13), '').encode('utf-8'))}/" in line
def _repo(tmp_path):
root = tmp_path / "repo"
root.mkdir()
(root / "ouroboros").mkdir()
(root / "ouroboros/size_ratchet_manifest.py").write_text(
'BASELINE_SOURCE_SHA = "' + "0" * 40 + '"\n'
'GIANT_PATHS = ()\nFUNCTION_DEBT = ()\nBAND_BASELINE_PATHS = ()\n'
'BAND_PATHS = {}\nBYTE_BASELINE_DEBT = {}\nBYTE_DEBT = {}\n', encoding="utf-8")
(root / "sample.py").write_text("def sample():\n return 1\n", encoding="utf-8")
subprocess.run(["git", "init", "-q"], cwd=root, check=True)
subprocess.run(["git", "add", "."], cwd=root, check=True)
subprocess.run(["git", "-c", "user.name=Test", "-c", "user.email=test@example.invalid",
"-c", "commit.gpgsign=false", "commit", "-qm", "fixture"], cwd=root, check=True)
return root
def test_real_readiness_keeps_information_out_of_warning_and_reuses_inventory(tmp_path, monkeypatch):
root = _repo(tmp_path)
information = []
assert review_helpers.check_worktree_readiness(root, information=information) == [
"No uncommitted changes detected — nothing to review."]
assert information == []
(root / "sample.py").write_text("# useful capacity fact\n" * (review.MAX_MODULE_LINES - 1), encoding="utf-8")
real_collect = review.collect_size_ratchet_inventory
calls = []
def collect(*args, **kwargs):
calls.append(args[0])
return real_collect(*args, **kwargs)
monkeypatch.setattr(review, "collect_size_ratchet_inventory", collect)
assert review_helpers.check_worktree_readiness(root, information=information) == []
assert len(calls) == 1
assert any("sample.py:" in line for line in information)
assert any("lines (1 remaining)" in line for line in information)
assert not any("size_ratchet_manifest.py:" in line for line in information)
calls.clear()
result = health._codebase_health(SimpleNamespace(repo_dir=root))
assert len(calls) == 1
assert "Size Headroom (information; official CI enforces the limits)" in result
assert "lines (1 remaining)" in result
assert "manifest is exact" in result

View file

@ -0,0 +1,120 @@
"""Real terminal producers feed dated disclosure without becoming admission policy."""
import json
from types import SimpleNamespace
from ouroboros import delegate_custody as custody
from ouroboros.context_runtime_facts import _delegation_capability_fact
from ouroboros.agent_task_pipeline import _store_task_result
from ouroboros.loop_llm_call import call_llm_with_retry
from ouroboros.subagent_history import record_task_execution, record_last_delegation, subagent_last_delegation
from ouroboros.subagent_runtime import resolve_configured_actor_dispatch
def _task(kind="api_model", target="openai::fixture"):
return {"id": "task-history", "type": "task", "configured_subagent": {
"schema": 1, "config_fingerprint": "irrelevant-list-hash", "selected_subagent_id": "worker",
"route": {"kind": kind, "target_id": target, "credential_profile_id": ""},
"effort": "high", "processing_preference": "standard"}}
def test_api_failure_fallback_and_retry_reach_next_task_context(tmp_path, monkeypatch):
monkeypatch.setattr("ouroboros.config.DATA_DIR", tmp_path)
task = _task()
usage = {}
class Provider:
broken = True
def chat(self, **_kwargs):
if self.broken:
raise RuntimeError("insufficient_quota")
return {"content": "Useful reply"}, {"prompt_tokens": 1, "completion_tokens": 1, "cost": 0.0}
provider = Provider()
args = (provider, [{"role": "user", "content": "work"}], "openai::fixture", None, "high", 0,
tmp_path / "logs", task["id"], 1, None, usage)
assert call_llm_with_retry(*args)[0] is None
failure = usage["llm_call_refs"][-1]
assert failure["failure_code"] == "quota_exhausted"
# Another model's usable response does not erase this route's own incident.
usage["llm_call_refs"].append({"model": "openai::fallback", "usable_solve_response": True})
_store_task_result(SimpleNamespace(drive_root=tmp_path), task, "Fallback answered", usage, {"tool_calls": []})
assert (tmp_path / "task_results" / "task-history.json").is_file()
row = _delegation_capability_fact()["subagents_last_executions"][0]
assert row["outcome"] == "failed" and row["occurred_at"] == failure["ts"]
assert row["task_id"] == task["id"] and row["fallback"]["model"] == "openai::fallback"
assert row["applied_model"] == ""
monkeypatch.setattr("ouroboros.provider_models.model_has_credentials", lambda *_: True)
assert resolve_configured_actor_dispatch(task, task_type="task").executor == "native"
provider.broken = False
assert call_llm_with_retry(*args)[0]["content"] == "Useful reply"
usage["_last_llm_call_meta"]["usable_solve_response"] = True
usage["execution_status"] = "failed" # a later code/test failure is not a provider failure
record_task_execution(task, usage, drive_root=tmp_path)
row = _delegation_capability_fact()["subagents_last_executions"][0]
assert row["outcome"] == "succeeded" and "failure_code" not in row
before = subagent_last_delegation(tmp_path)
record_task_execution(task, usage, drive_root=tmp_path)
assert subagent_last_delegation(tmp_path) == before
def test_recovery_settlement_and_refused_start_share_history(tmp_path, monkeypatch):
monkeypatch.setattr("ouroboros.config.DATA_DIR", tmp_path)
request = {"model": "fixture", "effort": "high", "credentialProfileId": "account-a"}
custody.record_start_requested(tmp_path, invocation_id="invoke", selected_subagent_id="worker",
route="codex", request=request, task_id="task-history")
custody.emit(tmp_path, custody.START_FAILED, {"invocation_id": "invoke", "definite": False,
"reason": "transport_unavailable"})
uncertain = subagent_last_delegation(tmp_path)
assert uncertain["outcome"] == "unknown"
custody.emit(tmp_path, custody.START_FAILED, {"invocation_id": "invoke", "definite": True,
"reason": "quota_exhausted"})
row = subagent_last_delegation(tmp_path)
assert row["outcome"] == "not_started" and row["requested_profile"] == "account-a"
assert row["observed_at"] == uncertain["observed_at"]
entry = custody.RunCustody(run_id="run-new", task_id="task-history", route_id="codex", model="fixture",
selected_subagent_id="worker", profile_id="account-a")
custody.record_started(tmp_path, entry)
detail = {"summary": {"state": "succeeded", "spendUsd": 0,
"finishedAt": "2099-09-18T12:00:00Z"}}
assert custody.settle_run(tmp_path, SimpleNamespace(), entry, detail)["settled"]
row = subagent_last_delegation(tmp_path)
assert row["outcome"] == "succeeded" and row["applied_model"] == ""
assert row["occurred_at"] == "2099-09-18T12:00:00Z"
assert custody.settle_run(tmp_path, SimpleNamespace(), entry, detail)["settled"]
assert subagent_last_delegation(tmp_path) == row
old = custody.RunCustody(run_id="old", task_id="old-task", route_id="codex", model="fixture",
selected_subagent_id="worker")
custody.record_started(tmp_path, old)
detail = {"summary": {"state": "failed", "spendUsd": 0,
"finishedAt": "2001-09-18T12:00:00Z", "failure": {"code": "quota_exhausted"}}}
custody.settle_run(tmp_path, SimpleNamespace(), old, detail)
assert subagent_last_delegation(tmp_path) == row
def test_old_corrupt_missing_and_unknown_time_are_not_health(tmp_path):
assert subagent_last_delegation(tmp_path) == {}
path = tmp_path / "state" / "subagent_last_delegation.json"
path.parent.mkdir()
path.write_text('{"run_id":"legacy","ts":"2001-01-01T00:00:00Z"}', encoding="utf-8")
assert subagent_last_delegation(tmp_path)["run_id"] == "legacy"
path.write_text("corrupt", encoding="utf-8")
record_last_delegation(route="codex", requested_model="fixture", applied_model="", run_id="unknown",
selected_subagent_id="worker", drive_root=tmp_path)
row = json.loads(path.read_text(encoding="utf-8"))
assert row["outcome"] == "unknown" and row["occurred_at"] == ""
assert row["observed_at"] and row["latest_by_subagent"]["worker"]["occurred_at"] == ""
def test_pre_invocation_session_refusal_is_dated_at_its_existing_producer(tmp_path, monkeypatch):
from ouroboros.subagent_bootstrap import _record_startup_refusal
monkeypatch.setattr("ouroboros.subagent_runtime.current_subagent_alternatives", lambda *_: [])
task = _task("agent_session", "codex=fixture")
ctx = SimpleNamespace()
_record_startup_refusal(ctx, task, reason="route_disabled")
_store_task_result(SimpleNamespace(drive_root=tmp_path), task, "Could not start", {}, {"tool_calls": []})
row = subagent_last_delegation(tmp_path)
assert row["outcome"] == "not_started" and row["failure_code"] == "route_disabled"
assert row["occurred_at"] == task["subagent_availability"]["observed_at"]
assert row["task_id"] == task["id"]

View file

@ -157,6 +157,7 @@
* @property {Object=} setup_contract
* @property {AvailableSubagentsSettingsMeta=} available_subagents
* @property {SettingsPolicyState=} policy_state
* @property {Object=} restart_state Component-owned pending restart/application facts.
*/
/**
@ -1171,6 +1172,17 @@
* @property {string=} applied_profile
* @property {string=} run_id
* @property {string=} ts
* @property {string=} occurred_at
* @property {string=} observed_at
* @property {string=} outcome
* @property {string=} failure_code
* @property {string=} reset_at
* @property {Object=} identity
* @property {Object<string, SubagentLastDelegation>=} latest_by_subagent
* @property {string=} task_id
* @property {string=} invocation_id
* @property {string=} attempt_id
* @property {Object=} fallback
*/
/**

View file

@ -538,7 +538,7 @@ export function describeExecutionEvidence(entry) {
if ('requested_model' in entry || 'applied_model' in entry) {
const parts = [];
const route = String(entry.route || '');
if (route) parts.push(`${route} session`);
if (route) parts.push(route === 'api_model' ? 'API model' : `${route} session`);
// Last-actual evidence is APPLIED telemetry only. Older receipts may
// retain the requested route while omitting what the harness actually
// served; never dress that requested value up as execution truth.
@ -551,6 +551,9 @@ export function describeExecutionEvidence(entry) {
const processing = processingExecutionText(entry.processing);
if (processing) parts.push(processing);
if (when) parts.push(when);
if (entry.outcome) parts.push(`${entry.outcome}${entry.failure_code ? ` (${entry.failure_code})` : ''}`);
if (entry.fallback?.model) parts.push(`fallback replied: ${entry.fallback.model}`);
if ('occurred_at' in entry) parts.push(entry.occurred_at || `observed ${entry.observed_at || entry.ts}; occurrence time unknown`);
return parts.join(' · ');
}
const effective = entry.effective || entry;

View file

@ -455,7 +455,8 @@ export function initSettings({ state, setBeforePageLeave, ws } = {}) {
const disposeSettingsTabs = bindSettingsTabs(page, { state });
bindSecretInputs(page);
bindEffortSegments(page);
const disposeLocalModel = bindLocalModelControls({ state });
const disposeLocalModel = bindLocalModelControls({ state,
onApplication: (local) => syncRestartState({ ...restartState, local_model: local }) });
// Best-effort About version from /api/health.
apiFetch('/api/health')
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`))))
@ -471,6 +472,8 @@ export function initSettings({ state, setBeforePageLeave, ws } = {}) {
let settingsDirty = false;
let draftRevision = 0;
let loadSequence = 0;
let restartReadSequence = 0;
let restartState = { restart_required: false };
let settingsSaving = false;
let saveOutcomeUnknown = false;
let validationAttempted = false;
@ -505,6 +508,24 @@ export function initSettings({ state, setBeforePageLeave, ws } = {}) {
setReviewerProcessingPreference(settings[PROCESSING_PREFERENCE_KEY], modelRoleMap(settings[MODEL_PROCESSING_PREFERENCES_KEY]));
}
function syncRestartState(value) {
if (!value || typeof value.restart_required !== 'boolean') return;
restartState = value;
byId('btn-restart-now').hidden = !value.restart_required;
const text = [value.summary, value.local_model?.summary].filter(Boolean).join(' ');
const target = byId('settings-restart-status');
target.hidden = !text;
setInlineStatus(target, text, value.restart_required || value.local_model?.pending_keys?.length ? 'warn' : 'muted');
}
async function refreshRestartState() {
const sequence = ++restartReadSequence;
try {
const data = await apiClient.settings();
if (sequence === restartReadSequence) syncRestartState(data?._meta?.restart_state);
} catch { /* An unavailable read cannot clear a known pending change. */ }
}
function syncSettingsLoadState() {
const saveBtn = byId('btn-save-settings');
if (saveBtn) {
@ -758,6 +779,7 @@ export function initSettings({ state, setBeforePageLeave, ws } = {}) {
async function loadSettings() {
const sequence = ++loadSequence;
const restartSequence = ++restartReadSequence;
const revision = draftRevision;
const [data, extData] = await Promise.all([
apiClient.settings(),
@ -766,6 +788,7 @@ export function initSettings({ state, setBeforePageLeave, ws } = {}) {
if (!data || typeof data !== 'object' || Array.isArray(data) || data.error) {
throw new Error(data?.error || 'The server did not return a settings document.');
}
if (restartSequence === restartReadSequence) syncRestartState(data._meta?.restart_state);
const sections = Array.isArray(extData?.live?.settings_sections)
? extData.live.settings_sections
: [];
@ -832,6 +855,7 @@ export function initSettings({ state, setBeforePageLeave, ws } = {}) {
async function refreshSettingsAfterExtensionChange(reason = 'skills changed') {
if (extensionRefreshPending || settingsSaving || saveOutcomeUnknown) return;
if (settingsDirty) {
await refreshRestartState();
setStatus(`Settings changed externally (${reason}). Reload after saving or discarding your draft.`, 'warn');
return;
}
@ -1165,6 +1189,7 @@ export function initSettings({ state, setBeforePageLeave, ws } = {}) {
refreshSettingsAfterExtensionChange(action);
});
}
const disposeRestartReconnect = ws?.on?.('open', refreshRestartState);
window.addEventListener('ouro:page-shown', (event) => {
if (event.detail?.page === 'settings') refreshSettingsAfterExtensionChange('settings page shown');
@ -1181,6 +1206,8 @@ export function initSettings({ state, setBeforePageLeave, ws } = {}) {
disposeSettingsTabs();
window.removeEventListener('beforeunload', beforeUnload);
disposeLocalModel();
disposeRestartReconnect?.();
restartReadSequence += 1;
baselineSettleDisposer?.();
modelRoles.destroy();
document.removeEventListener('settings-model-catalog:updated', onModelCatalog);
@ -1264,10 +1291,6 @@ export function initSettings({ state, setBeforePageLeave, ws } = {}) {
await reloadSettingsWithFeedback();
});
// #285: true from a restart-required save until the restart command is
// actually sent — keeps the Restart now affordance across later saves.
let restartPending = false;
byId('btn-save-settings').addEventListener('click', async () => {
if (settingsSaving || saveOutcomeUnknown) return;
if (!settingsLoaded) {
@ -1306,9 +1329,6 @@ export function initSettings({ state, setBeforePageLeave, ws } = {}) {
settingsSaving = true;
setButtonBusy(saveButton, true);
setStatus('Saving…', 'muted');
// A pending restart LATCHES: a later save that needs no restart must
// not hide the button while the process still runs the old config.
if (!restartPending) byId('btn-restart-now')?.setAttribute('hidden', '');
let saved = false;
try {
const data = await apiClient.saveSettings(body);
@ -1371,6 +1391,8 @@ export function initSettings({ state, setBeforePageLeave, ws } = {}) {
} else if (data.restart_required) {
statusMsg = 'Settings saved. Some changes require a restart to take effect';
statusType = 'warn';
} else if (data.restart_state?.local_model?.summary) {
statusMsg = 'Settings saved';
} else if (data.immediate_changed && data.next_task_changed) {
statusMsg = 'Settings saved. Some changes took effect immediately; others apply on the next task';
} else if (data.immediate_changed) {
@ -1431,10 +1453,8 @@ export function initSettings({ state, setBeforePageLeave, ws } = {}) {
statusType = 'warn';
}
setStatus(statusMsg, statusType);
if (data.restart_required || runtimeModeResult?.restart_required) {
restartPending = true;
}
if (restartPending) byId('btn-restart-now')?.removeAttribute('hidden');
syncRestartState(data.restart_state);
await refreshRestartState();
window.dispatchEvent(new CustomEvent('ouro:settings-updated', { detail: { reason: 'settings saved', source: 'settings' } }));
} catch (e) {
const receipt = e?.body || e?.payload;
@ -1455,8 +1475,6 @@ export function initSettings({ state, setBeforePageLeave, ws } = {}) {
byId('btn-restart-now')?.addEventListener('click', async () => {
const outcome = await confirmAndSendRestart({ openConfirmDialog, ws });
if (outcome === 'sent') {
restartPending = false;
byId('btn-restart-now')?.setAttribute('hidden', '');
setStatus('Restart requested. If the agent refuses, the reason appears in the main chat.', 'muted');
} else if (outcome === 'not_connected') {
setStatus('Not connected — the restart command was not sent.', 'warn');

View file

@ -45,15 +45,18 @@ function setProgressBar(fraction) {
bar.setAttribute('aria-valuenow', Math.round(fraction * 100));
}
export function bindLocalModelControls({ state }) {
export function bindLocalModelControls({ state, onApplication } = {}) {
let destroyed = false;
let stopPending = false;
let ready = false;
let statusSequence = 0;
async function updateLocalStatus() {
if (destroyed || state.activePage !== 'settings') return;
const sequence = ++statusSequence;
try {
const d = await fetchJson('/api/local-model/status', { cache: 'no-store' });
if (destroyed) return;
if (destroyed || sequence !== statusSequence) return;
if (d.settings_application && onApplication) onApplication(d.settings_application);
const el = document.getElementById('local-model-status');
if (!el) return;
const isReady = d.status === 'ready';
@ -75,6 +78,7 @@ export function bindLocalModelControls({ state }) {
if (d.runtime_status === 'install_ok') text += ' — Runtime installed ✓';
if (d.runtime_status === 'install_error') text += ' — Runtime install failed';
if (d.error && !isInstalling) text += ' — ' + d.error;
if (d.settings_application?.pending_keys?.length) text += ' — Saved changes need Stop, then Start.';
el.textContent = text;
el.dataset.tone = isReady ? 'ok' : (d.status === 'error' || d.runtime_status === 'install_error' ? 'error' : 'muted');
@ -119,6 +123,7 @@ export function bindLocalModelControls({ state }) {
}
setTestResult('');
setProgressBar(null);
setInlineStatus(document.getElementById('local-model-action-status'), '', 'muted');
try {
const resp = await apiFetch('/api/local-model/start', {
method: 'POST',

View file

@ -909,6 +909,7 @@ export function renderSettingsPage() {
</div>
<div class="settings-footer-status">
<span id="settings-unsaved-indicator" class="settings-inline-status settings-unsaved-indicator" aria-hidden="true">Unsaved changes</span>
<div id="settings-restart-status" class="settings-inline-status" role="status" aria-live="polite" hidden></div>
<div id="settings-status" class="settings-inline-status" role="status" aria-live="polite" aria-atomic="true"></div>
</div>
</div>

View file

@ -221,7 +221,8 @@ export function rowStatus(row, state) {
const ROUTE_HINT = 'Choose how this subagent runs: an API model or an agent session.';
function executionFor(snapshot, subagentId) {
const receipt = snapshot?.subagent_last_delegation;
const history = snapshot?.subagent_last_delegation;
const receipt = history?.latest_by_subagent?.[subagentId] || history;
if (!receipt || typeof receipt !== 'object') return null;
return String(receipt.selected_subagent_id || '') === String(subagentId || '')
? receipt : null;
@ -238,13 +239,21 @@ export function rowMeta(row, state, errors) {
// An empty draft (`openai::` with no model yet) is still an invitation.
if (!String(row.route?.target_id || '').trim()
|| (!session && !routeModelFields(row.route).model.trim())) return { text: ROUTE_HINT, tone: '' };
const evidence = describeExecutionEvidence(executionFor(state.snapshot, row.subagent_id));
const receipt = executionFor(state.snapshot, row.subagent_id);
const evidence = describeExecutionEvidence(receipt);
const identity = receipt?.identity;
const sameRoute = identity && identity.kind === row.route.kind
&& identity.target_id === row.route.target_id
&& identity.credential_profile_id === String(routePin(row.route) || '')
&& identity.effort === String(row.effort || '')
&& identity.processing_preference === String(row.processing_preference || state.processingPreference || '');
const historyLabel = identity ? (sameRoute ? 'Last run' : 'Earlier settings') : 'Last actual run';
// The exact stored spelling is disclosed here, where it informs, and never
// in a placeholder, where it would instruct (docs/DESIGN.md §7). A session
// target already reads as harness plus model in its own controls.
const saved = session ? '' : `stored as ${String(row.route.target_id).trim()}`;
return {
text: [saved, evidence ? `Last actual run: ${evidence}` : ''].filter(Boolean).join(' · '),
tone: '',
text: [saved, evidence ? `${historyLabel}: ${evidence}` : ''].filter(Boolean).join(' · '),
tone: '', ...(evidence ? { history: true } : {}),
};
}

View file

@ -366,7 +366,7 @@ export function availableSubagentRowMarkup(row, state, index = 0) {
${effortSelectHtml(`data-subagent-field="effort" aria-label="Reasoning effort for Subagent ${ordinal}"`, row.effort || '', 'route default')}
</div>
${processingDetailsHtml(`data-subagent-field="processing_preference" aria-label="Processing for Subagent ${ordinal}"`, row.processing_preference, state.processingPreference)}
<div id="actor-${escapeHtml(rowKey)}-meta" class="available-subagent-meta ui-field-help" data-subagent-meta${meta.tone ? ` data-tone="${escapeHtml(meta.tone)}"` : ''} title="${escapeHtml(meta.text)}"${meta.text ? '' : ' hidden'}>${escapeHtml(meta.text)}</div>
<div id="actor-${escapeHtml(rowKey)}-meta" class="available-subagent-meta ui-field-help" data-subagent-meta${meta.history ? ' data-run-history' : ''}${meta.tone ? ` data-tone="${escapeHtml(meta.tone)}"` : ''} title="${escapeHtml(meta.text)}"${meta.text ? '' : ' hidden'}>${escapeHtml(meta.text)}</div>
</article>`;
}
@ -498,6 +498,7 @@ export function createAvailableSubagentsEditor({
const metaEl = el.querySelector('[data-subagent-meta]');
if (!metaEl) return;
Object.assign(metaEl, { hidden: !meta.text, textContent: meta.text, title: meta.text });
metaEl.toggleAttribute('data-run-history', Boolean(meta.history));
if (meta.tone) metaEl.dataset.tone = meta.tone;
else delete metaEl.dataset.tone;
});
@ -507,7 +508,6 @@ export function createAvailableSubagentsEditor({
if (state.saveAttempted) onJudged(!shown.length);
}
// Judge existing rows on Save/Finish; later new rows remain fresh.
function noteSaveAttempt() {
state.saveAttempted = true;
state.setting.items.forEach((row) => { row._uiAttempted = true; });

View file

@ -940,6 +940,11 @@ textarea::placeholder {
text-overflow: ellipsis;
}
.available-subagent-meta[data-run-history] {
white-space: normal;
overflow-wrap: anywhere;
}
.available-subagent-actions .btn-default {
min-height: 34px;
padding: 0 11px;

View file

@ -576,6 +576,11 @@
text-overflow: ellipsis;
}
.available-subagent-meta[data-run-history] {
white-space: normal;
overflow-wrap: anywhere;
}
.available-subagent-actions .btn-default {
min-height: 34px;
padding: 0 11px;

View file

@ -599,6 +599,29 @@ test('preview replaces only a clean generated baseline', () => {
assert.equal(editor.setting.items[0].subagent_id, 'codex_builder');
});
test('dated API failures stay informational and bind to the exact execution choices', () => {
const row = apiRow({ processing_preference: 'standard' });
const state = { snapshot: { subagent_last_delegation: { latest_by_subagent: {
api_scout: { selected_subagent_id: 'api_scout', route: 'api_model',
requested_model: row.route.target_id, applied_model: '', outcome: 'failed',
failure_code: 'quota_exhausted', ts: '2026-09-18T12:00:00Z', occurred_at: '2026-09-18T12:00:00Z',
identity: { ...row.route, credential_profile_id: '', effort: 'high', processing_preference: 'standard' } },
} } } };
const meta = rowMeta(row, state, []);
assert.equal(meta.tone, '');
assert.match(meta.text, /Last run: API model.*failed \(quota_exhausted\).*2026-09-18/);
assert.equal(rowMeta({ ...row, recommended_use: 'Changed description' }, state, []).text, meta.text);
for (const changed of [
{ ...row, effort: 'low' },
{ ...row, processing_preference: 'flex' },
{ ...row, route: { ...row.route, target_id: 'another-model' } },
{ ...row, route: { ...row.route, credential_profile_id: 'another-account' } },
]) assert.match(rowMeta(changed, state, []).text, /Earlier settings:/);
const oldStatus = rowStatus(row, state);
delete state.snapshot.subagent_last_delegation;
assert.deepEqual(rowStatus(row, state), oldStatus, 'history never changes live admission/status');
});
test('a typed preview refusal stays typed and cannot become an empty fictional draft', () => {
const editor = createAvailableSubagentsEditor({ doc: null, win: null });
editor.setPreviewFailure({