ouroboros/supervisor/events_budget.py
Anton 612067385c WIP: preserve #1196 continuity repairs before upstream integration
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-24 05:09:55 +03:00

849 lines
43 KiB
Python

"""Usage accounting and budget-pause events reported by workers.
One owner for folding a worker's reported usage into the ledger and for the two
budget fences a paused root raises: the pause itself and the admission fence
that keeps its descendants out of the queue — including the per-row HOLD that
survives lifting that fence, and the explicit selection that releases one row
(#1196, owner Q9).
"""
from __future__ import annotations
import logging
import pathlib
import time
import uuid
from typing import Any, Dict, Optional
from ouroboros.utils import append_jsonl, utc_now_iso
from ouroboros.task_results import STATUS_SCHEDULED, write_task_result
log = logging.getLogger(__name__)
from supervisor.log_addressing import address_ctx_event as _address_ctx
from supervisor.log_addressing import address_task_event as _address_task_event
def _handle_llm_usage(evt: Dict[str, Any], ctx: Any) -> None:
usage_raw = evt.get("usage")
usage: Dict[str, Any] = usage_raw if isinstance(usage_raw, dict) else {}
# Real-progress signal (activity model): a completed LLM round is genuine work,
# not just process liveness. Stamp last_progress_at so the timeout enforcer keeps
# an actively-working task alive (distinct from the 30s liveness heartbeat).
_tid = str(evt.get("task_id") or "")
_running = getattr(ctx, "RUNNING", None)
if _tid and isinstance(_running, dict):
_m = _running.get(_tid)
# Mutate IN PLACE — _m is the same object RUNNING already holds. A write-back
# (`_running[_tid] = _m`) would resurrect a task a cross-thread cancel popped
# between the get and the write; mutating a popped dict is simply harmless.
if isinstance(_m, dict):
_m["last_progress_at"] = time.time()
# Task-tree attribution: the durable llm_usage row declares
# root/parent/delegation/lane fields, but worker-side emitters do
# not know the queue lineage. The supervisor DOES — fill the gaps
# from the authoritative RUNNING record so per-tree cost rollups
# over events.jsonl become possible (emitter-supplied values win).
_task = _m.get("task") if isinstance(_m.get("task"), dict) else {}
for _field in (
"root_task_id", "parent_task_id", "delegation_role",
"task_group_id", "requested_model_lane", "effective_model_lane",
):
if not evt.get(_field) and _task.get(_field):
evt[_field] = str(_task.get(_field))
# Normalize usage across loop.py, web_search, and delegated-run producers.
# Tolerant coercion: one malformed token field must not raise and drop the
# whole round from the budget ledger and events.jsonl (the exception would
# be swallowed by dispatch_event and the cost silently lost).
def _tolerant_int(*candidates: Any) -> int:
for value in candidates:
if value in (None, ""):
continue
try:
return int(float(value))
except (TypeError, ValueError):
log.warning("llm_usage: non-numeric token field %r ignored", value)
return 0
prompt_tokens = _tolerant_int(
usage.get("prompt_tokens"), usage.get("input_tokens"), evt.get("prompt_tokens")
)
completion_tokens = _tolerant_int(
usage.get("completion_tokens"), usage.get("output_tokens"), evt.get("completion_tokens")
)
cached_tokens = _tolerant_int(usage.get("cached_tokens"), evt.get("cached_tokens"))
cache_write_tokens = _tolerant_int(
usage.get("cache_write_tokens"), evt.get("cache_write_tokens")
)
prompt_cache_ttl = str(
usage.get("prompt_cache_ttl")
or evt.get("prompt_cache_ttl")
or ""
)
ledger_attempt_ids = [
str(value)
for value in (usage.get("ledger_attempt_ids") or evt.get("ledger_attempt_ids") or [])
if value
]
raw_cost = usage.get("cost")
if raw_cost is None:
raw_cost = evt.get("cost")
cost_known = raw_cost not in (None, "")
try:
resolved_cost = float(raw_cost) if cost_known else None
except (TypeError, ValueError):
resolved_cost = None
cost_known = False
usage_for_budget = {
**usage,
"cost": resolved_cost,
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
"cached_tokens": cached_tokens,
"cache_write_tokens": cache_write_tokens,
"prompt_cache_ttl": prompt_cache_ttl,
}
projection_update_status = "available"
try:
if ctx.update_budget_from_usage(usage_for_budget) is False:
projection_update_status = "unavailable"
except Exception:
projection_update_status = "unavailable"
log.error("Paid llm_usage retained but compatibility projection update failed", exc_info=True)
# Server-side web-search citations ({url,title,content}, capped at 20 in
# llm.py). Persisted so post-hoc audits (e.g. the GAIA leakage audit) can see
# what the native web-search tool actually fetched — the search happens on the
# provider side and never appears in tools.jsonl.
web_search_sources = usage.get("web_search_sources")
# Host-owned sealed-reasoning pin fact (issue #468): why same-model provider
# failover was withheld on this call. Bounded {"sealed", "artifact"} dict.
reasoning_pin = usage.get("reasoning_pin")
# Provider wire projection / clamp of the requested reasoning effort
# ({requested, applied, reason, model}); persisted so the owner can audit
# what tier the provider actually received.
effort_clamped = usage.get("reasoning_effort_clamped")
usage_event = {
"ts": evt.get("ts", utc_now_iso()),
"type": "llm_usage",
"task_id": evt.get("task_id", ""),
"root_task_id": evt.get("root_task_id", ""),
"parent_task_id": evt.get("parent_task_id", ""),
"delegation_role": evt.get("delegation_role", ""),
"task_group_id": evt.get("task_group_id", ""),
"requested_model_lane": evt.get("requested_model_lane", evt.get("model_lane", "")),
"effective_model_lane": evt.get("effective_model_lane", ""),
"category": evt.get("category", "other"),
"model": evt.get("model", ""),
"api_key_type": evt.get("api_key_type", ""),
"model_category": evt.get("model_category", "other"),
"provider": evt.get("provider", ""),
"source": evt.get("source", ""),
**{key: evt[key] for key in ("llm_call_id", "execution_id", "round_id", "round") if key in evt},
"cost_estimated": bool(evt.get("cost_estimated", False)),
"cost": resolved_cost,
"cost_known": cost_known,
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
"cached_tokens": cached_tokens,
"cache_write_tokens": cache_write_tokens,
"prompt_cache_ttl": prompt_cache_ttl,
"accounting_authority": "physical_attempt_ledger",
"projection_update_status": projection_update_status,
"ledger_attempt_ids": ledger_attempt_ids,
**({"chat_id": evt["chat_id"]} if evt.get("chat_id") is not None else {}),
**({"web_search_sources": web_search_sources} if isinstance(web_search_sources, list) and web_search_sources else {}),
**({"reasoning_pin": reasoning_pin} if isinstance(reasoning_pin, dict) and reasoning_pin else {}),
**({"reasoning_effort_clamped": effort_clamped} if isinstance(effort_clamped, dict) and effort_clamped else {}),
}
_address_ctx(ctx, usage_event)
try:
append_jsonl(ctx.DRIVE_ROOT / "logs" / "events.jsonl", usage_event)
except Exception:
log.warning("Failed to log llm_usage event to events.jsonl", exc_info=True)
# ONE live frame (sink copy suppressed).
try:
ctx.bridge.push_log(usage_event)
except Exception:
log.debug("Failed to forward llm_usage to live logs", exc_info=True)
def _set_root_budget_pause_locked(root_task_id: str, pause: Dict[str, Any]) -> Dict[str, Any]:
"""Install the sole root-budget admission marker; caller holds queue lock."""
from supervisor import queue as queue_mod
root_task_id = str(root_task_id or "").strip()
if not root_task_id:
raise ValueError("root budget pause requires root_task_id")
existing = queue_mod.BUDGET_ROOT_FENCES.get(root_task_id)
root_rows = list(queue_mod.PENDING) + [m.get("task", {}) for m in queue_mod.RUNNING.values()]
resumed = any(str(t.get("id") or "") == root_task_id and budget_fence_selected(t, existing)
for t in root_rows)
row = {
"status": "paused",
"scope": "root",
"root_task_id": root_task_id,
"fence_id": str(
pause.get("fence_id")
or (None if resumed else (existing or {}).get("fence_id"))
or uuid.uuid4().hex
),
"auto_resume": False,
"paused_at": str(
pause.get("paused_at")
or (existing or {}).get("paused_at")
or utc_now_iso()
),
}
queue_mod.BUDGET_ROOT_FENCES[root_task_id] = row
if not existing or existing.get("fence_id") != row["fence_id"]:
from supervisor.budget_resume import revoke_exact_budget_resume
for member in queue_mod.PENDING:
if str(member.get("root_task_id") or "") != root_task_id or member.get("id") == root_task_id:
continue
if isinstance(member.get("_budget_pause_resume"), dict):
revoke_exact_budget_resume(member, "new_root_budget_fence")
hold = member.get(BUDGET_HOLD_KEY)
if isinstance(hold, dict) and hold.get("selected"):
hold_budget_row(member, reason=HOLD_ROOT_FENCE_LIFTED,
extra={"root_task_id": root_task_id, "fence_id": row["fence_id"]})
return row
def _handle_budget_pause(evt: Dict[str, Any], ctx: Any) -> None:
"""Move a paused task back to the same durable queue generation.
Two shapes share this seam: the historical replay-safe ZERO-dispatch pause
and the exact continuation (#1196), whose worker wrote a durable
``budget_pause`` row before unwinding. The exact shape is validated against
THAT row, never against the event's prose.
"""
task_id = str(evt.get("task_id") or "")
pause = evt.get("resource_limit") if isinstance(evt.get("resource_limit"), dict) else {}
exact = bool(pause.get("exact_continuation")) and isinstance(pause.get("checkpoint"), dict)
if exact:
install_exact_budget_pause(ctx, task_id, pause["checkpoint"], evt=evt)
return
if (
not task_id
or not bool(pause.get("replay_safe"))
or pause.get("physical_calls") != 0
):
raise ValueError("budget pause requires a replay-safe zero-dispatch task")
from supervisor.queue import _queue_lock
with _queue_lock:
if str(pause.get("scope") or "") == "root":
root_row = _set_root_budget_pause_locked(
str(pause.get("root_task_id") or evt.get("root_task_id") or ""),
pause,
)
pause = {
**pause,
**root_row,
"status": "paused_before_dispatch",
"replay_safe": True,
"physical_calls": 0,
}
meta = ctx.RUNNING.pop(task_id, None)
task = meta.get("task") if isinstance(meta, dict) and isinstance(meta.get("task"), dict) else None
if task is None:
raise RuntimeError(f"budget-paused task is not running: {task_id}")
resumed_task = dict(task)
resumed_task["_budget_pause"] = dict(pause)
if not any(str(item.get("id") or "") == task_id for item in ctx.PENDING):
ctx.PENDING.append(resumed_task)
ctx.sort_pending()
worker_id = evt.get("worker_id")
if worker_id in ctx.WORKERS and ctx.WORKERS[worker_id].busy_task_id == task_id:
ctx.WORKERS[worker_id].busy_task_id = None
try:
write_task_result(
ctx.DRIVE_ROOT,
task_id,
STATUS_SCHEDULED,
reason_code="budget_exhausted",
resource_limit=pause,
result="Task paused before its first model dispatch; explicit resume or cancel required.",
)
except Exception:
log.warning("Failed to persist budget pause for %s", task_id, exc_info=True)
event = {
"ts": evt.get("ts", utc_now_iso()),
"type": "budget_scope_paused",
"task_id": task_id,
"task_type": evt.get("task_type") or task.get("type"),
"owner_visible": True,
"toast_once": f"{task_id}:budget-paused:{pause.get('scope') or 'global'}",
**pause,
}
_address_task_event({task_id: meta} if isinstance(meta, dict) else None, ctx.DRIVE_ROOT, event)
append_jsonl(ctx.DRIVE_ROOT / "logs" / "events.jsonl", event)
ctx.persist_queue_snapshot(reason="budget_pause_before_dispatch")
try:
ctx.bridge.push_log(event)
except Exception:
log.warning("Failed to forward budget pause to Activity", exc_info=True)
def install_exact_budget_pause(ctx: Any, task_id: str, checkpoint: Dict[str, Any], *,
evt: Dict[str, Any] | None = None,
source: str = "worker_event") -> Dict[str, Any]:
"""Park the SAME task id under its exact continuation: RUNNING -> PENDING.
Order: durable row must already say ``pausing`` for this pause_id/attempt
(the worker wrote it before raising) -> queue transition -> snapshot ->
row ``paused``. The WHOLE order — including the confirmation, the owner
projection and the event — runs under the queue lock a Resume grant also
holds, and the confirmation compare-and-sets the pause id AND its state, so
a park that completes late can never overwrite a grant or republish a
resumed task as paused (#1196, F1). A snapshot that cannot be persisted
leaves the row at ``pausing``: the pause is real (the source exists) but not
confirmed, and the next persisted snapshot or a restore completes it —
never a fake ``paused``. Also the crash-during-pausing completion path (``source``
names it), which is why nothing here reads the worker's event body — with
ONE exception: a direct owner-chat turn was never in RUNNING, so its event
carries the turn's own task record (``task`` beside ``_is_direct_chat``),
and THAT record is what the SAME task id is parked under (#1196). The row
check above still decides; the record only supplies the queue row.
"""
from ouroboros.budget_pause import (
STATE_PAUSED, STATE_PAUSING, budget_pause_row, exact_pause_marker, parkable_direct_task,
set_budget_pause,
)
from supervisor import queue as queue_mod
from supervisor.queue import _queue_lock
evt = evt or {}
# The event-loop ctx and the reaper's pool module expose the same queue
# state; the snapshot/sort seams fall back to the queue module's own.
sort_pending = getattr(ctx, "sort_pending", None) or queue_mod.sort_pending
persist_snapshot = getattr(ctx, "persist_queue_snapshot", None) or queue_mod.persist_queue_snapshot
pause_id = str(checkpoint.get("pause_id") or "")
if not task_id or not pause_id:
raise ValueError("exact budget pause requires task_id and pause_id")
with _queue_lock:
meta = ctx.RUNNING.get(task_id)
task = meta.get("task") if isinstance(meta, dict) and isinstance(meta.get("task"), dict) else None
direct_turn = False
if task is None and evt.get("_is_direct_chat") and isinstance(evt.get("task"), dict) \
and str(evt["task"].get("id") or "") == task_id:
# A direct turn ends its live actor on the way out (no second live
# actor for this id); the queue row is minted from its own record.
task = parkable_direct_task(evt["task"])
meta = {"task": task, "attempt": int(task.get("_attempt") or 1), "worker_id": None}
direct_turn = True
if task is None:
raise RuntimeError(f"budget-paused task is not running: {task_id}")
result_root = pathlib.Path(task.get("budget_drive_root") or ctx.DRIVE_ROOT)
row = budget_pause_row(result_root, task_id)
attempt = int(meta.get("attempt") or task.get("_attempt") or 1)
if (row.get("pause_id") != pause_id or row.get("state") not in {STATE_PAUSING, STATE_PAUSED}
or int(row.get("task_attempt") or 0) != attempt or not row.get("source_ref")):
raise ValueError(f"exact budget pause {pause_id} has no live durable row for attempt {attempt}")
marker = exact_pause_marker(row, default_root=str(task.get("root_task_id") or task_id))
if marker["scope"] == "root":
fence = _set_root_budget_pause_locked(marker["root_task_id"], marker)
marker = {**marker, "fence_id": fence["fence_id"]}
ctx.RUNNING.pop(task_id, None)
paused_task = dict(task)
paused_task["_budget_pause"] = marker
if direct_turn:
stamp_direct_queue_row(paused_task, queue_mod)
if not any(str(item.get("id") or "") == task_id for item in ctx.PENDING):
ctx.PENDING.append(paused_task)
sort_pending()
worker_id = evt.get("worker_id") if evt else meta.get("worker_id")
if worker_id in ctx.WORKERS and ctx.WORKERS[worker_id].busy_task_id == task_id:
ctx.WORKERS[worker_id].busy_task_id = None
# The confirmation, the status projection and the owner-visible event
# stay under the SAME queue lock as the park. Resume holds that lock for
# its whole grant, so a late park completion can no longer publish a
# stale ``paused`` over a live grant; the state CAS below is the second
# rail, for a writer this lock does not cover (#1196, F1).
persisted = persist_snapshot(reason="budget_pause_exact_continuation")
confirmed, superseded = False, ""
if persisted:
try:
set_budget_pause(result_root, task_id, {**row, "state": STATE_PAUSED,
"paused_confirmed_at": time.time(),
"pause_source": source},
expected_pause_id=pause_id,
expected_state=(STATE_PAUSING, STATE_PAUSED))
confirmed = True
except Exception:
log.warning("Exact budget pause row for %s was not confirmed 'paused'",
task_id, exc_info=True)
superseded = _pause_row_superseded(result_root, task_id, pause_id)
else:
log.error("Exact budget pause for %s parked in memory but its snapshot was not persisted; "
"the durable row stays 'pausing' until a later snapshot confirms it", task_id)
if not superseded:
# A row this park no longer owns keeps whatever the newer writer
# projected: a resumed task must never read as paused again.
try:
write_task_result(
result_root, task_id, STATUS_SCHEDULED,
reason_code="budget_paused", resource_limit=marker,
result=("Task paused exactly at a completed boundary (budget). Cumulative spend, rounds "
"and execution time are retained; an explicit owner Resume continues the same task."),
)
except Exception:
log.warning("Failed to persist exact budget pause status for %s", task_id, exc_info=True)
event = {
"ts": (evt or {}).get("ts", utc_now_iso()),
"type": "budget_scope_paused",
"task_id": task_id,
"task_type": (evt or {}).get("task_type") or task.get("type"),
"owner_visible": True,
"toast_once": f"{task_id}:budget-paused:{pause_id}",
"pause_source": source,
"park_state": STATE_PAUSED if confirmed else STATE_PAUSING,
**{key: value for key, value in marker.items() if key != "checkpoint"},
"pause_id": pause_id,
"external_runs": [
{k: run.get(k) for k in ("run_id", "state", "stop_outcome")}
for run in ((row.get("external_runs") or {}).get("runs") or []) if isinstance(run, dict)
],
}
if superseded:
# Not an owner-visible pause: the durable row moved on (a grant, a
# newer pause, an abandoned row). The anomaly is recorded as itself.
event = {key: value for key, value in event.items() if key != "toast_once"}
event.update(type="budget_pause_park_superseded", owner_visible=False,
park_state=superseded)
_address_task_event({task_id: meta} if isinstance(meta, dict) else None, ctx.DRIVE_ROOT, event)
append_jsonl(ctx.DRIVE_ROOT / "logs" / "events.jsonl", event)
try:
bridge = getattr(ctx, "bridge", None)
if bridge is not None:
bridge.push_log(event)
except Exception:
log.warning("Failed to forward exact budget pause to Activity", exc_info=True)
return marker
def _pause_row_superseded(result_root: pathlib.Path, task_id: str, pause_id: str) -> str:
"""Why a park confirmation may NOT be published, or "" when it is only late.
A write that failed transiently leaves the row at ``pausing`` for THIS pause
id — the pause is real, merely unconfirmed, and the ordinary projection
still belongs to it. A row that names another pause, carries a grant, or is
no longer live belongs to a newer writer and is never written over.
"""
from ouroboros.budget_pause import STATE_PAUSED, STATE_PAUSING, budget_pause_row
try:
row = budget_pause_row(result_root, task_id)
except Exception:
return "pause_record_unreadable"
if str(row.get("pause_id") or "") != str(pause_id):
return "pause_identity_changed"
state = str(row.get("state") or "")
return "" if state in {STATE_PAUSING, STATE_PAUSED} else (state or "pause_record_missing")
def _handle_budget_resume_child(evt: Dict[str, Any], ctx: Any) -> None:
"""Owner Q9: a resumed root's model SELECTS one budget-paused child to continue.
The requester must be the live parent/root of the target (its tree, not any
tree); the grant itself goes through the ONE resume seam, so every typed
refusal (money, cancel intent, deadline, lifetime, root still paused) is the
same the owner would receive. The outcome is recorded as an event the
requesting task can read back; nothing is auto-fanned-out.
"""
from supervisor.queue import _queue_lock
from supervisor.queue_transitions import resume_budget_paused_task
task_id = str(evt.get("task_id") or "").strip()
requester = str(evt.get("requested_by") or "").strip()
with _queue_lock:
target = next((row for row in ctx.PENDING if str(row.get("id") or "") == task_id), None)
lineage_ok = bool(
target is not None and requester
and requester in (str(target.get("parent_task_id") or ""), str(target.get("root_task_id") or ""))
)
if not lineage_ok:
outcome: Dict[str, Any] = {"ok": False, "error": "not_a_budget_paused_descendant"}
else:
# A MODEL-issued selection: the grant additionally requires the live
# owner-derived Resume grant of the target's own root (never lineage alone).
outcome = resume_budget_paused_task(task_id, selected_by=requester)
event = {
"ts": evt.get("ts", utc_now_iso()),
"type": "budget_resume_child_outcome",
"task_id": task_id,
"requested_by": requester,
"reason": str(evt.get("reason") or "")[:500],
**{key: value for key, value in outcome.items() if key != "task_id"},
}
_address_ctx(ctx, event)
append_jsonl(ctx.DRIVE_ROOT / "logs" / "events.jsonl", event)
try:
ctx.bridge.push_log(event)
except Exception:
log.debug("Failed to forward child resume outcome to Activity", exc_info=True)
def _handle_budget_root_fence(evt: Dict[str, Any], ctx: Any) -> None:
"""Latch one root after a refused dispatch; never reconcile its subtree."""
task_id = str(evt.get("task_id") or "").strip()
supplied = evt.get("resource_limit") if isinstance(evt.get("resource_limit"), dict) else {}
root_task_id = str(supplied.get("root_task_id") or evt.get("root_task_id") or "").strip()
if not task_id or not root_task_id or str(supplied.get("scope") or "") != "root":
raise ValueError("root budget fence requires task_id, root_task_id, and root scope")
from supervisor.queue import _queue_lock
with _queue_lock:
fence = _set_root_budget_pause_locked(root_task_id, supplied)
ctx.persist_queue_snapshot(reason="budget_root_fenced")
event = {
"ts": evt.get("ts", utc_now_iso()),
"type": "budget_scope_paused",
"task_id": task_id,
"task_type": evt.get("task_type"),
"owner_visible": True,
"toast_once": f"{root_task_id}:budget-paused:root",
**fence,
}
_address_ctx(ctx, event)
append_jsonl(ctx.DRIVE_ROOT / "logs" / "events.jsonl", event)
try:
ctx.bridge.push_log(event)
except Exception:
log.warning("Failed to forward root budget pause to Activity", exc_info=True)
# --- budget admission HOLDS (#1196) --------------------------------------------
#
# The root fence above keeps a paused root's descendants out of the queue. When
# that root is resumed the fence is LIFTED, which must not by itself make its
# zero-dispatch siblings assignable: each one takes the durable hold below until
# the model selects it through the same resume control (owner Q9). The same
# marker carries a row whose exact continuation could not be restored and one
# whose spent grant could not be revoked, so an un-dispatchable row is always a
# typed, visible fact instead of a dropped or silently runnable task.
BUDGET_HOLD_KEY = "_budget_pause_hold"
# The typed hold reasons (one vocabulary for the queue row, the task result and
# the events log). A hold beside a retained ``_budget_pause`` marker is released
# by a successful exact grant (the grant re-validates the durable authority);
# a hold on a marker-less row is released only by an explicit selection.
HOLD_ROOT_FENCE_LIFTED = "root_fence_lifted_pending_selection"
# One MEMBER of a tree whose root latch is still up: the latch belongs to the
# root, so releasing this row must not lift it for every sibling (owner Q9).
# The selection recorded on the row names the fence it was granted against.
HOLD_ROOT_FENCE_MEMBER_SELECTION = "root_fence_member_pending_selection"
HOLD_RESTORE_REFUSED_PREFIX = "restore_refused:"
HOLD_ROOT_ACCEPTANCE_FENCED = "root_acceptance_fenced_at_restore"
HOLD_REVOCATION_UNWRITTEN = "resume_grant_revocation_unwritten"
HOLD_RECORD_UNREADABLE_AT_REVOKE = "pause_record_unreadable_at_revoke"
HOLD_STALE_GRANT_SUPERSEDED = "stale_resume_grant_superseded"
HOLD_MALFORMED_RESUME_IDENTITY = "malformed_resume_identity"
HOLD_RESTART_REVOCATION_UNWRITTEN = "restart_revocation_unwritten"
# A queue row still carrying a grant handoff whose grant the durable row says
# was CONSUMED: the task ran on. The row is stale, never re-armed as a pause
# and never dispatched; a restart fences it as the running work it names.
HOLD_GRANT_CONSUMED_STALE_ROW = "stale_queue_row_grant_consumed"
# Malformed acceptance-fence evidence in the snapshot fails the restore closed
# for ordinary rows; a saved exact pause is retained under this hold instead.
HOLD_INVALID_ACCEPTANCE_FENCE_SNAPSHOT = HOLD_RESTORE_REFUSED_PREFIX + "invalid_acceptance_fence_snapshot"
HOLD_INVALID_BUDGET_FENCE_SNAPSHOT = HOLD_RESTORE_REFUSED_PREFIX + "invalid_budget_fence_snapshot"
def stamp_direct_queue_row(task: Dict[str, Any], queue_mod: Any) -> None:
"""Give a parked direct turn the queue-order facts ``enqueue_task`` would have.
The direct lane never enqueued it, so the row has no sequence, priority or
``queued_at``; without them the sort key and the census phase have nothing
to read. Called with the queue lock held.
"""
counter = getattr(queue_mod, "QUEUE_SEQ_COUNTER_REF", None)
if isinstance(counter, dict) and "_queue_seq" not in task:
counter["value"] = int(counter.get("value") or 0) + 1
task["_queue_seq"] = counter["value"]
if "priority" not in task:
try:
task["priority"] = queue_mod.coerce_queue_order(
None, queue_mod._task_priority(str(task.get("type") or "task")))
except Exception:
task["priority"] = 1
task.setdefault("queued_at", utc_now_iso())
task["_is_direct_chat"] = True
def budget_hold_fact(task) -> Optional[Dict[str, Any]]:
"""The durable NON-dispatch hold on one queued row (#1196), or ``None``.
Three shapes share it and none invents a checkpoint identity (no pause_id,
no ``exact_continuation``): a zero-dispatch sibling whose paused root's
admission fence was lifted by that root's Resume — lifting the fence must
not make it assignable, the model selects it explicitly (owner Q9) — a row
whose exact continuation could not be restored, and a row whose spent
grant could not be revoked. ``selected`` is the only release.
"""
hold = task.get(BUDGET_HOLD_KEY) if isinstance(task, dict) else None
return hold if isinstance(hold, dict) and not hold.get("selected") else None
def budget_fence_selected(task: Any, fence: Any) -> bool:
"""Whether THIS row carries an explicit selection recorded against THIS fence.
A root's admission latch keeps a whole tree out of the queue. The owner (or,
under the root's live grant, the model) may select ONE member of that tree
without lifting the latch for its siblings: the selection rides the row's own
hold and names the fence generation it was granted against, so a later fence
— a root that paused again — is never pre-released by an older selection
(#1196, owner Q9).
"""
hold = task.get(BUDGET_HOLD_KEY) if isinstance(task, dict) else None
fence_id = str((fence or {}).get("fence_id") or "") if isinstance(fence, dict) else ""
return bool(isinstance(hold, dict) and hold.get("selected") and fence_id
and str(hold.get("fence_id") or "") == fence_id)
def budget_resume_dispatch_allowed(q: Any, task: Dict[str, Any]) -> bool:
"""An exact child selection belongs to the CURRENT root grant and fence only."""
handoff = task.get("_budget_pause_resume")
if not isinstance(handoff, dict):
return True
root_id = str(task.get("root_task_id") or task.get("id") or "")
fence_id = str((q.BUDGET_ROOT_FENCES.get(root_id) or {}).get("fence_id") or "")
if fence_id != str(handoff.get("root_fence_id") or ""):
return False
if root_id == str(task.get("id") or ""):
return True
root_grant = live_root_resume_grant(q, root_id, pathlib.Path(task.get("budget_drive_root") or q.DRIVE_ROOT))
return bool(handoff.get("selected_by") == "owner" and not handoff.get("root_grant_id") and not root_grant
or root_grant and handoff.get("root_grant_id") == root_grant["grant_id"]
and handoff.get("root_resume_generation") == root_grant["generation"])
def hold_budget_row(task: Dict[str, Any], *, reason: str, detail: str = "",
extra: Optional[Dict[str, Any]] = None,
result_root: Optional[pathlib.Path] = None) -> Dict[str, Any]:
"""Hold one queued row: typed, visible, never dropped and never cancelled.
The row stays PENDING with its own identity; any spent resume handoff is
removed so no stale grant can dispatch, and the typed reason is projected
onto the task result so the owner and the model read the same fact.
"""
hold = {"reason": str(reason), "detail": str(detail or "")[:300], "held_at": utc_now_iso(),
"selected": False, "dispatchable": False, **(extra or {})}
task.pop("_budget_pause_resume", None)
task[BUDGET_HOLD_KEY] = hold
if result_root is not None:
try:
write_task_result(
result_root, str(task.get("id") or ""), STATUS_SCHEDULED,
reason_code="budget_paused",
resource_limit={"status": "budget_hold", "auto_resume": False,
"exact_continuation": False,
"resume_policy": "explicit_selection_same_seam", **hold},
)
except Exception:
log.debug("Budget hold projection failed for %s", task.get("id"), exc_info=True)
return hold
def hold_restored_budget_pause(task: Dict[str, Any], drive_root: Any, *, reason: str,
detail: str = "") -> Dict[str, Any]:
"""Restore-time hold for a paused row the restart could not clear for dispatch.
A corrupt, missing or refused checkpoint source, or an acceptance fence over
the root, does not drop or cancel the task: the row stays PENDING and
un-dispatchable with a typed reason, and it KEEPS its ``_budget_pause``
marker — the marker is the locator the owner's Resume validates against the
durable authority. A later Resume re-reads that authority: a source that is
readable again is granted (releasing this hold), one that is not refuses
typed and leaves the saved pause where it is.
"""
prior = task.get("_budget_pause") if isinstance(task.get("_budget_pause"), dict) else {}
hold_budget_row(
task, reason=reason,
detail=detail or "the exact budget-pause continuation could not be cleared for dispatch after a restart",
extra={"pause_id": str((prior.get("checkpoint") or {}).get("pause_id") or ""),
"root_task_id": str(prior.get("root_task_id") or task.get("root_task_id") or "")},
result_root=pathlib.Path(task.get("budget_drive_root") or drive_root),
)
return task
def release_budget_hold(task: Dict[str, Any], *, released_by: str, reason: str) -> Optional[Dict[str, Any]]:
"""Mark a hold RELEASED in place; returns the prior hold for rollback, or ``None``.
Reached only from the exact grant, which has just re-validated the pause's
durable authority: the hold's own facts are kept on the row (``selected``
flips, nothing is erased) so the release stays auditable.
"""
hold = task.get(BUDGET_HOLD_KEY) if isinstance(task.get(BUDGET_HOLD_KEY), dict) else None
if hold is None or hold.get("selected"):
return None
task[BUDGET_HOLD_KEY] = {**hold, "selected": True, "selected_at": utc_now_iso(),
"selected_by": str(released_by or "owner"), "released_reason": str(reason or "")}
return hold
def hold_root_resume_descendants(q: Any, root_id: str, fence: dict, grant: dict) -> tuple:
"""Replace lifted-fence eligibility with per-child holds; retain exact pauses.
A fence-derived marker is not a checkpoint: convert it to a selection hold
before removing its fence, or it would be stranded. Older exact child grants
are revoked and selected again under this root grant. Return hold/marker
changes for the Resume transaction's snapshot rollback; revocations stay safe.
"""
from supervisor.budget_resume import revoke_exact_budget_resume
held, markers = [], {}
for member in q.PENDING:
member_id = str(member.get("id") or "")
if not member_id or member_id == root_id or str(member.get("root_task_id") or "") != root_id:
continue
if isinstance(member.get("_budget_pause_resume"), dict):
revoke_exact_budget_resume(member, "root_resume_generation_changed")
pause = member.get("_budget_pause")
fence_derived = bool(isinstance(pause, dict) and not pause.get("exact_continuation")
and pause.get("scope") == "root" and pause.get("root_task_id") == root_id
and pause.get("fence_id") == fence.get("fence_id"))
if (pause is not None and not fence_derived) or isinstance(member.get(BUDGET_HOLD_KEY), dict):
continue
if fence_derived:
markers[member_id] = member.pop("_budget_pause")
hold_budget_row(
member, reason=HOLD_ROOT_FENCE_LIFTED,
detail="root resumed; this zero-dispatch sibling awaits explicit selection",
extra={"root_task_id": root_id, "root_grant_id": grant["grant_id"],
"root_resume_generation": grant["generation"],
**({"replaced_fence_marker": True} if fence_derived else {})},
result_root=pathlib.Path(member.get("budget_drive_root") or q.DRIVE_ROOT))
held.append(member_id)
rebound = refresh_root_grant_holds(q.PENDING, root_id, grant_id=grant["grant_id"], generation=grant["generation"])
return held, markers, rebound
def refresh_root_grant_holds(pending: Any, root_task_id: str, *, grant_id: str,
generation: int) -> Dict[str, Dict[str, Any]]:
"""Re-bind every unselected fence-lifted hold of ``root_task_id`` to its NEW grant.
A root that paused again and was resumed again carries a newer grant; a
sibling held from the earlier Resume must be selectable under the live one,
not refused forever as stale. Returns the prior holds keyed by task id for
rollback when the snapshot cannot be persisted.
"""
prior: Dict[str, Dict[str, Any]] = {}
for member in pending:
hold = member.get(BUDGET_HOLD_KEY) if isinstance(member, dict) and isinstance(member.get(BUDGET_HOLD_KEY), dict) else None
if (hold is None or hold.get("selected") or str(hold.get("reason") or "") != HOLD_ROOT_FENCE_LIFTED
or str(hold.get("root_task_id") or member.get("root_task_id") or "") != root_task_id
or str(hold.get("root_grant_id") or "") == grant_id):
continue
prior[str(member.get("id") or "")] = dict(hold)
member[BUDGET_HOLD_KEY] = {**hold, "root_grant_id": grant_id, "root_resume_generation": int(generation),
"rebound_at": utc_now_iso()}
return prior
def select_held_budget_row(q: Any, task: Dict[str, Any], hold: Dict[str, Any],
*, selected_by: str) -> Dict[str, Any]:
"""Record an explicit selection on a held row (queue lock held).
The hold is released ONLY here, and only for a row that still proves it
never dispatched. A model-issued selection additionally needs the live
owner-derived Resume grant of its root: lifting a fence granted eligibility,
never continuation.
"""
task_id = str(task.get("id") or "")
result_root = pathlib.Path(task.get("budget_drive_root") or q.DRIVE_ROOT)
root_task_id = str(hold.get("root_task_id") or task.get("root_task_id") or task_id)
if hold.get("reason") not in {HOLD_ROOT_FENCE_LIFTED, HOLD_ROOT_FENCE_MEMBER_SELECTION}:
return {"ok": False, "error": str(hold.get("reason") or "budget_hold_unresolved")}
if selected_by:
root_grant = live_root_resume_grant(q, root_task_id, result_root)
if not root_grant:
return {"ok": False, "error": "root_resume_grant_missing",
"root_task_id": root_task_id, "action": "resume_root_first"}
if (hold.get("root_grant_id") and str(hold["root_grant_id"]) != root_grant["grant_id"]):
return {"ok": False, "error": "root_resume_generation_stale",
"root_task_id": root_task_id, "action": "resume_root_first"}
from supervisor.queue_transitions import pending_member_replay_safe
safe, unsafe_error = pending_member_replay_safe(q, task)
if not safe:
return {"ok": False, "error": unsafe_error, "action": "cancel_or_new_run"}
selection = {**hold, "selected": True, "selected_at": utc_now_iso(),
"selected_by": str(selected_by or "owner")}
if task_id == root_task_id:
selection.update(root_grant_id=uuid.uuid4().hex, root_resume_generation=1)
prior_pause = task.pop("_budget_pause", None)
task[BUDGET_HOLD_KEY] = selection
if not q.persist_queue_snapshot(reason="budget_hold_selected"):
task[BUDGET_HOLD_KEY] = dict(hold)
if prior_pause is not None:
task["_budget_pause"] = prior_pause
return {"ok": False, "error": "snapshot_not_persisted"}
try:
write_task_result(result_root, task_id, STATUS_SCHEDULED, reason_code="",
resource_limit={"status": "resumed", "auto_resume": False,
"exact_continuation": False, **selection})
except Exception:
log.debug("Failed to project held-row selection for %s", task_id, exc_info=True)
q.append_jsonl(
q.DRIVE_ROOT / "logs" / "events.jsonl",
{"ts": utc_now_iso(), "type": "budget_hold_selected", "task_id": task_id,
"root_task_id": root_task_id, "selected_by": selection["selected_by"],
"hold_reason": str(hold.get("reason") or "")},
)
return {"ok": True, "task_id": task_id, "root_task_id": root_task_id,
"selection": "budget_hold_released", "same_generation": True}
def live_root_resume_grant(q: Any, root_task_id: str, result_root: pathlib.Path) -> Dict[str, Any]:
"""The root's CURRENT owner-derived Resume grant, or ``{}`` (owner Q9).
A descendant continues only under a root Resume that is still live: the
root's durable pause row must carry a granted-or-consumed, unrevoked grant.
A root that paused again after that grant opens a NEW generation, so a late
selection carrying the old one finds nothing live and is refused.
"""
from ouroboros.budget_pause import STATE_RESUME_GRANTED, STATE_RESUMED, budget_pause_row
root_task_id = str(root_task_id or "").strip()
if not root_task_id:
return {}
rows = [row for row in q.PENDING
if isinstance(row, dict) and str(row.get("id") or "") == root_task_id]
rows += [meta.get("task") for meta in q.RUNNING.values()
if isinstance(meta, dict) and isinstance(meta.get("task"), dict)
and str(meta["task"].get("id") or "") == root_task_id]
fence = q.BUDGET_ROOT_FENCES.get(root_task_id)
if fence:
# Legacy zero-dispatch root Resume selects only the root. Its selection
# grants child eligibility while the fence continues holding siblings.
for task in rows:
hold = task.get(BUDGET_HOLD_KEY) or {}
if budget_fence_selected(task, fence) and hold.get("root_grant_id"):
return {"grant_id": hold["root_grant_id"],
"generation": hold["root_resume_generation"], "pause_id": ""}
return {}
root_drive = next((str(row.get("budget_drive_root")) for row in rows
if isinstance(row, dict) and row.get("budget_drive_root")), "")
try:
row = budget_pause_row(pathlib.Path(root_drive or result_root), root_task_id)
except Exception:
log.debug("Root resume grant unreadable for %s", root_task_id, exc_info=True)
return {}
grant = row.get("grant") if isinstance(row.get("grant"), dict) else {}
if (row.get("state") not in {STATE_RESUME_GRANTED, STATE_RESUMED}
or not str(row.get("pause_id") or "").strip()
or not str(grant.get("grant_id") or "").strip() or grant.get("revoked_at")):
return {}
return {"grant_id": str(grant["grant_id"]),
"generation": int(row.get("resume_generation") or 0),
"pause_id": str(row.get("pause_id") or "")}