ouroboros/supervisor/events_schedule_task.py
Anton 2cf296b6ba Preserve scheduled child intent through supervisor projections
Reuse the declared subagent intent fields through admission, worker payloads and queue snapshots so explicit effort reaches dispatch. Exercise the real scheduling path through restore, first session start and durable result readback.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-10-10 22:51:18 +03:00

721 lines
34 KiB
Python

"""The schedule_task admission gates and their refusals.
One owner for the facts the dispatch parent's schedule handler needs: the
chat-target gate, the depth and worker-pool gates, the active-child cap, the
composed queue payload, and every refusal path including worktree cleanup for
a rejected subagent.
"""
from __future__ import annotations
import logging
from typing import Any, Dict, Optional
from ouroboros.tool_capabilities import ACTING_SUBAGENT_MODE
from ouroboros.task_results import (
STATUS_FAILED,
write_task_result,
)
from ouroboros.utils import utc_now_iso
from supervisor.events_subagent_admission import (
_send_subagent_rejection,
)
from ouroboros.config import MAX_ACTIVE_SUBAGENTS_HARD_CAP
from ouroboros.config import get_max_active_subagents_per_root
from ouroboros.contracts.task_contract import build_task_contract
from ouroboros.contracts.task_contract import normalize_allowed_resources
from ouroboros.subagents import intended_lane as intended_subagent_lane, subagent_intent_fields
from ouroboros.task_results import STATUS_SCHEDULED
from ouroboros.tools.control_delegation import admitted_depth_cap
from ouroboros.tools.control_delegation import check_delegation_admission
from ouroboros.tools.control_delegation import durable_direct_child_count
from ouroboros.tools.control_delegation import stamp_depth_provenance
from supervisor.events_subagent_admission import _active_subagent_count
from supervisor.events_subagent_admission import _compose_subagent_text
from supervisor.events_subagent_admission import _record_delegation_constraint
from supervisor.events_subagent_admission import _resolve_subagent_constraint
from supervisor.events_subagent_admission import _subagent_cap_blocks
from supervisor.events_subagent_admission import _subagent_scheduled_meta
from supervisor.log_addressing import bound_project_chat_id as _bound_project_chat_id
from supervisor.message_bus import coerce_chat_identity, notification_chat_route
from ouroboros.contracts.chat_id_policy import HIDDEN_CHAT_ID
from supervisor.task_dispatch import build_scheduled_task_payload as _build_scheduled_task_payload
import uuid
log = logging.getLogger(__name__)
def _events():
"""The parent module, read at call time.
The parent owns the rebindable module state and the members tests
monkeypatch there; reading them through the module at each call keeps
one binding, where a from-import would freeze the value this leaf saw
at import time (the owner-approved D18/D33 mechanical exception).
"""
from supervisor import events
return events
VALID_SUBAGENT_MEMORY_MODES = frozenset({"forked", "empty"})
def _cleanup_rejected_worktree(tid: str, result_fields: Dict[str, Any]) -> None:
"""Tear down a write surface provisioned for an acting subagent that is then
rejected by a later gate, so rejected schedules never leak a worktree or an
empty genesis project."""
tc = result_fields.get("task_constraint") if isinstance(result_fields, dict) else None
if not (isinstance(tc, dict) and tc.get("mode") == ACTING_SUBAGENT_MODE):
return
surface = str(tc.get("surface") or "")
write_root = str(tc.get("write_root") or "").strip()
if not write_root:
return
try:
from ouroboros import subagent_worktrees
if surface == "self_worktree":
subagent_worktrees.remove_worktree(task_id=str(tid))
elif surface == "genesis":
subagent_worktrees.remove_genesis_project(write_root)
except Exception:
log.debug("Failed to clean up rejected acting write surface for %s", tid, exc_info=True)
def _reject_schedule_task(
ctx: Any,
*,
tid: str,
chat_id: int,
delegation_role: str,
parent_id: Any,
root_task_id: str,
role: str,
result_fields: Dict[str, Any],
detail: str,
status: str = STATUS_FAILED,
fallback_message: str = "",
reason_code: Optional[str] = None,
extra_fields: Optional[Dict[str, Any]] = None,
persist_result: bool = True,
) -> None:
"""Clean up and notify a rejection, preserving an existing result when asked."""
log.warning("Rejecting scheduled task %s: %s", tid, detail)
write_fields = {**result_fields, **(extra_fields or {})}
never_admitted = write_fields.get("admission_outcome") == "never_admitted"
refusal_confirmed = False
if persist_result and not never_admitted:
_cleanup_rejected_worktree(tid, result_fields)
if reason_code:
write_fields["reason_code"] = reason_code
if persist_result:
try:
from supervisor.task_admission import persist_never_admitted_refusal
write_refusal = persist_never_admitted_refusal if never_admitted else write_task_result
write_refusal(
ctx.DRIVE_ROOT,
tid,
**({} if never_admitted else {"status": status}),
**write_fields,
result=detail,
# ABI-3: a rejected schedule spent a confirmed zero — stamped
# under the honest name (the retired alias is read-only).
accounted_upper_bound_usd=0.0,
)
refusal_confirmed = True
except Exception:
log.warning("Failed to persist schedule rejection for %s", tid, exc_info=True)
if refusal_confirmed and never_admitted:
_cleanup_rejected_worktree(tid, result_fields)
try:
from ouroboros.headless import remove_subagent_task_drive
from supervisor.queue import task_settlement_interlock, task_settlement_liveness
remove_subagent_task_drive(ctx.DRIVE_ROOT, tid, live=task_settlement_liveness,
guard=task_settlement_interlock, admission_rollback=True)
except Exception:
log.warning("Refused child drive cleanup deferred for %s", tid, exc_info=True)
# A torn-down notification bus must not escape into the supervisor loop.
try:
if chat_id is not None:
if delegation_role == "subagent":
_send_subagent_rejection(
ctx,
chat_id,
tid=tid,
parent_id=parent_id,
root_task_id=root_task_id,
role=role,
status=status,
detail=detail,
)
elif fallback_message:
ctx.send_with_budget(chat_id, fallback_message, role="system", system_type="task_admission_notice")
except Exception:
log.warning("Failed to notify schedule rejection for %s", tid, exc_info=True)
def _handle_schedule_task(evt: Dict[str, Any], ctx: Any) -> None:
st = ctx.load_state()
owner_chat_id = st.get("owner_chat_id")
try:
owner_chat_int = int(owner_chat_id or 0)
except (TypeError, ValueError):
owner_chat_int = 0
# Membership, not truthiness (C4): an explicit 0 is the hidden partition the
# producer stamped; only a MISSING id is absence and falls to the owner chat.
chat_id = coerce_chat_identity(evt.get("chat_id"), owner_chat_int)
tid = str(evt.get("task_id") or uuid.uuid4().hex[:8])
created_at = utc_now_iso() if not evt.get("task_id") else ""
desc = str(evt.get("objective") or evt.get("description") or "").strip()
expected_output = str(evt.get("expected_output") or "").strip()
constraints = str(evt.get("constraints") or "").strip()
role = str(evt.get("role") or "").strip()
task_context = str(evt.get("context") or "").strip()
parent_id = evt.get("parent_task_id")
root_task_id = str(evt.get("root_task_id") or parent_id or tid)
session_id = str(evt.get("session_id") or "")
actor_id = str(evt.get("actor_id") or "ouroboros")
delegation_role = str(evt.get("delegation_role") or "subagent")
# Idempotency/ownership is checked before parsing for every scheduling role
# so a malformed replay cannot terminalize an already-owned task id. Fresh
# events still reach the typed depth rejection below before provisioning or
# enqueue; events without an explicit id use the normal fresh-id path.
from supervisor.task_admission import subagent_schedule_preflight
if subagent_schedule_preflight(
ctx, evt, chat_id, delegation_role=delegation_role,
):
return
from supervisor.task_admission import parse_schedule_task_depth
depth, depth_rejected = parse_schedule_task_depth(
ctx,
evt,
tid=tid,
chat_id=chat_id,
delegation_role=delegation_role,
parent_id=parent_id,
root_task_id=root_task_id,
role=role,
desc=desc,
expected_output=expected_output,
constraints=constraints,
task_context=task_context,
)
if depth_rejected:
return
memory_mode = str(evt.get("memory_mode") or "").strip()
drive_root = str(evt.get("drive_root") or "").strip()
child_drive_root = str(evt.get("child_drive_root") or drive_root).strip()
budget_drive_root = str(evt.get("budget_drive_root") or "").strip()
root_cost_ceiling_usd = evt.get("root_cost_ceiling_usd")
# Forward parent-requested intent; dispatch resolves it once.
requested_model_lane = str(evt.get("requested_model_lane") or evt.get("model_lane") or "auto").strip() or "auto"
parent_model_lane = str(evt.get("parent_model_lane") or "").strip()
requested_executor = str(evt.get("requested_executor") or "").strip().lower() or "auto"
intent = subagent_intent_fields(evt)
intent.update(requested_model_lane=requested_model_lane, parent_model_lane=parent_model_lane,
requested_executor=requested_executor)
# The event cannot supply the lane this admission has yet to verify.
intent.pop("required_model_lane", None)
task_group_id = str(evt.get("task_group_id") or "").strip()
task_group = evt.get("task_group") if isinstance(evt.get("task_group"), dict) else {}
subagent_envelope = evt.get("subagent_envelope") if isinstance(evt.get("subagent_envelope"), dict) else {}
configured_subagent = evt.get("configured_subagent") if isinstance(evt.get("configured_subagent"), dict) else {}
parent_cognitive_route = evt.get("parent_cognitive_route") if isinstance(evt.get("parent_cognitive_route"), dict) else {}
task_constraint = evt.get("task_constraint") if isinstance(evt.get("task_constraint"), dict) else None
required_capabilities = [
str(item or "").strip().lower()
for item in (evt.get("required_capabilities") if isinstance(evt.get("required_capabilities"), list) else [])
if str(item or "").strip()
]
workspace_root = str(evt.get("workspace_root") or "").strip()
workspace_mode = str(evt.get("workspace_mode") or "").strip()
project_id = str(evt.get("project_id") or "").strip()
project_basis = None
if project_id:
from ouroboros.projects_registry import project_admission_view, project_binding_for_task, validate_project_admission
try:
parent = (getattr(ctx, "RUNNING", {}).get(str(parent_id)) or {}).get("task") or {}
inherited_basis = parent.get("_project_admission")
if "_project_admission" in parent:
validate_project_admission(inherited_basis)
binding = project_binding_for_task(ctx.DRIVE_ROOT, root_task_id, strict=True) or {}
project_basis = (dict(inherited_basis) if isinstance(inherited_basis, dict)
and inherited_basis.get("project_id") == project_id else
project_admission_view(ctx.DRIVE_ROOT, project_id,
allow_unregistered=binding.get("project_id") != project_id,
frozen=True))
except (OSError, ValueError, RuntimeError) as exc:
_reject_schedule_task(
ctx, tid=tid, chat_id=chat_id, delegation_role=delegation_role,
parent_id=parent_id, root_task_id=root_task_id, role=role,
result_fields={"project_id": project_id, "child_drive_root": child_drive_root},
detail=f"Task not scheduled: Project state could not be checked: {exc}",
reason_code=getattr(exc, "reason", "project_routing_fence_lookup_failed"),
extra_fields={"admission_outcome": "never_admitted"})
return
acting_reject_detail = ""
if delegation_role == "subagent":
task_constraint, workspace_root, workspace_mode, acting_reject_detail = _resolve_subagent_constraint(
ctx, tid=tid, requested_constraint=task_constraint, workspace_root=workspace_root,
workspace_mode=workspace_mode, base_sha=str(evt.get("base_sha") or ""), parent_task_id=str(parent_id or ""))
workspace_copy = {}
if (not acting_reject_detail and isinstance(task_constraint, dict)
and task_constraint.get("surface") == "self_worktree"):
from ouroboros.workspace_copies import admitted_copy_metadata
workspace_copy = admitted_copy_metadata(workspace_root)
allowed_resources = normalize_allowed_resources(evt.get("allowed_resources") or {})
task_contract = evt.get("task_contract") if isinstance(evt.get("task_contract"), dict) else build_task_contract({
"id": tid,
"type": "task",
"description": desc,
"objective": desc,
"expected_output": expected_output,
"constraints": constraints,
"workspace_root": workspace_root,
"workspace_mode": workspace_mode,
"allowed_resources": allowed_resources,
"parent_task_id": parent_id,
"root_task_id": root_task_id,
"session_id": session_id,
"delegation_role": delegation_role,
})
if delegation_role == "subagent" and not acting_reject_detail:
task_contract = {**task_contract, "workspace": {
**task_contract.get("workspace", {}), "root": workspace_root, "mode": workspace_mode}}
live_max_depth = _events().get_max_subagent_depth()
max_depth = admitted_depth_cap(task_contract, live_max_depth)
task_contract, depth_provenance = stamp_depth_provenance(
task_contract,
attempted_depth=depth,
max_depth=max_depth,
)
result_fields = {
**({"created_at": created_at} if created_at else {}),
"parent_task_id": parent_id,
"root_task_id": root_task_id,
"session_id": session_id,
"actor_id": actor_id,
"delegation_role": delegation_role,
"role": role,
"description": desc,
"objective": desc,
"expected_output": expected_output,
"constraints": constraints,
"context": task_context,
"workspace_root": workspace_root,
"workspace_mode": workspace_mode, "project_id": project_id,
"parent_workspace": evt.get("parent_workspace"),
**({"workspace_copy": workspace_copy} if workspace_copy else {}),
**{key: evt[key] for key in ("directory_strategy", "scope_paths") if key in evt},
"allowed_resources": allowed_resources,
"task_contract": task_contract,
"depth_provenance": depth_provenance,
"chat_id": chat_id,
"memory_mode": memory_mode,
"drive_root": drive_root,
"child_drive_root": child_drive_root,
"budget_drive_root": budget_drive_root,
"root_cost_ceiling_usd": root_cost_ceiling_usd,
"task_constraint": task_constraint,
"required_capabilities": required_capabilities,
"model_lane": requested_model_lane,
**intent,
"task_group_id": task_group_id,
"task_group": task_group,
"subagent_envelope": subagent_envelope,
"configured_subagent": configured_subagent,
"parent_cognitive_route": parent_cognitive_route,
}
if delegation_role == "subagent":
parent_budget = _events()._parent_delegation_budget(
ctx, parent_id, budget_drive_root or getattr(ctx, "DRIVE_ROOT", "")
)
count_roots = [budget_drive_root or getattr(ctx, "DRIVE_ROOT", "")]
canonical_root = getattr(ctx, "DRIVE_ROOT", "")
if canonical_root and str(canonical_root) != str(count_roots[0]):
count_roots.append(canonical_root)
direct_child_counts = [
durable_direct_child_count(root, parent_id, exclude_task_id=tid)
for root in count_roots
]
direct_child_count = (
max(count for count in direct_child_counts if count is not None)
if direct_child_counts and all(
count is not None for count in direct_child_counts
)
else None
)
rights = check_delegation_admission(
parent_budget,
direct_child_count=direct_child_count,
)
if not rights.ok:
detail = f"Subagent rejected: {rights.reason_code}: {rights.detail}"
result_fields["delegation_admission"] = {
"status": "rejected",
"reason_code": rights.reason_code,
"direct_child_count": rights.direct_child_count,
}
_reject_schedule_task(
ctx,
tid=tid,
chat_id=chat_id,
delegation_role=delegation_role,
parent_id=parent_id,
root_task_id=root_task_id,
role=role,
result_fields=result_fields,
detail=detail,
reason_code=rights.reason_code,
)
return
if delegation_role == "subagent" and (not str(evt.get("objective") or "").strip() or not expected_output):
detail = "Subagent rejected: schedule_subagent requires objective and expected_output."
log.warning("Rejected subagent due to strict schedule_subagent schema violation: task_id=%s", tid)
_reject_schedule_task(
ctx, tid=tid, chat_id=chat_id, delegation_role=delegation_role,
parent_id=parent_id, root_task_id=root_task_id, role=role,
result_fields={**result_fields, "objective": str(evt.get("objective") or "").strip()},
detail=detail,
)
return
if delegation_role == "subagent" and acting_reject_detail:
log.warning("Acting subagent request rejected: task_id=%s detail=%s", tid, acting_reject_detail[:160])
_record_delegation_constraint(
root_task_id,
task_id=tid,
role=role,
directive="block_surface",
scope={"surface": str((task_constraint or {}).get("surface") or evt.get("write_surface") or "")},
rationale=acting_reject_detail,
advisory=True,
)
_reject_schedule_task(
ctx, tid=tid, chat_id=chat_id, delegation_role=delegation_role,
parent_id=parent_id, root_task_id=root_task_id, role=role,
result_fields=result_fields, detail=acting_reject_detail,
)
return
if delegation_role == "subagent" and (memory_mode not in VALID_SUBAGENT_MEMORY_MODES or not child_drive_root):
detail = (
"Subagent rejected: internal schedule_subagent events must use memory_mode=forked or empty "
"and include a child_drive_root."
)
log.warning("Rejected subagent due to invalid child-drive contract: task_id=%s memory_mode=%s child_drive_root=%s", tid, memory_mode, child_drive_root)
_reject_schedule_task(
ctx, tid=tid, chat_id=chat_id, delegation_role=delegation_role,
parent_id=parent_id, root_task_id=root_task_id, role=role,
result_fields=result_fields, detail=detail,
)
return
# The lane an applicable, non-advisory require_lane constraint verified this
# admission against (F9): stamped onto the child record so the dispatch-time
# policy default cannot override the lane the gate just enforced.
required_model_lane = ""
if delegation_role == "subagent":
try:
from ouroboros.tool_access import subagent_profile_satisfies
from ouroboros.tools.control_delegation import effective_delegation_budget
from ouroboros.task_tree_ledger import open_delegation_constraints
selected_profile = (
"acting_subagent"
if isinstance(task_constraint, dict)
and task_constraint.get("mode") == ACTING_SUBAGENT_MODE
and task_constraint.get("surface")
else "local_readonly_subagent"
)
_ok, missing_caps = subagent_profile_satisfies(selected_profile, required_capabilities)
constraints_for_tree = open_delegation_constraints(root_task_id)
decision = effective_delegation_budget(
task_contract.get("delegation_budget") if isinstance(task_contract, dict) else {},
missing_capabilities=missing_caps,
unresolved_constraints=constraints_for_tree,
write_surface=str((task_constraint or {}).get("surface") or "") if isinstance(task_constraint, dict) else "",
role=role,
requested_lane=requested_model_lane,
intended_lane=intended_subagent_lane(requested_model_lane, parent_model_lane),
active_child_count=_active_subagent_count(root_task_id, getattr(ctx, "PENDING", []), getattr(ctx, "RUNNING", {})),
)
if not decision.ok:
detail = f"Subagent rejected: {decision.reason_code}: {decision.detail}"
_reject_schedule_task(
ctx, tid=tid, chat_id=chat_id, delegation_role=delegation_role,
parent_id=parent_id, root_task_id=root_task_id, role=role,
result_fields=result_fields, detail=detail,
)
return
if isinstance(task_contract, dict) and decision.budget:
task_contract = {**task_contract, "delegation_budget": decision.budget}
result_fields["task_contract"] = task_contract
required_model_lane = str(getattr(decision, "required_lane", "") or "")
except Exception:
log.debug("Delegation reconciliation failed open for %s", tid, exc_info=True)
intent["required_model_lane"] = required_model_lane
result_fields.update(intent)
if depth > max_depth:
detail = f"Subagent rejected: subtask depth limit ({max_depth}) exceeded."
log.warning("Rejected task due to depth limit: depth=%d, desc=%s", depth, desc[:100])
_reject_schedule_task(
ctx, tid=tid, chat_id=chat_id, delegation_role=delegation_role,
parent_id=parent_id, root_task_id=root_task_id, role=role,
result_fields=result_fields,
detail=detail,
fallback_message=f"⚠️ Task rejected: subtask depth limit ({max_depth}) exceeded",
)
return
# Fail fast when the worker pool is disabled (e.g. after a crash storm put
# the supervisor in direct-chat mode). Without this, the task is written as
# 'scheduled' and enqueued but nothing can ever run it — a permanent "ghost"
# the parent keeps polling. Give the parent a clear terminal signal instead
# so it can do the work inline.
if desc and not (getattr(ctx, "WORKERS", {}) or {}):
_reject_schedule_task(
ctx, tid=tid, chat_id=chat_id, delegation_role=delegation_role,
parent_id=parent_id, root_task_id=root_task_id, role=role,
result_fields=result_fields,
detail=(
"Subagent not scheduled: the worker pool is currently unavailable "
"(workers_unavailable), likely disabled after repeated worker crashes "
"(direct-chat mode). It was NOT left scheduled — do the work inline "
"yourself, or retry after /restart."
),
reason_code="workers_unavailable",
fallback_message=f"⚠️ Task {tid} not scheduled: worker pool unavailable.",
)
return
if desc:
from supervisor.queue import PENDING as QUEUE_PENDING, RUNNING as QUEUE_RUNNING
pending_ref = getattr(ctx, "PENDING", QUEUE_PENDING)
running_ref = getattr(ctx, "RUNNING", QUEUE_RUNNING)
max_active = get_max_active_subagents_per_root()
queued_behind_active_cap = False
if delegation_role == "subagent" and _subagent_cap_blocks(root_task_id, parent_id, pending_ref, running_ref, max_active):
active_count = _active_subagent_count(root_task_id, pending_ref, running_ref)
if active_count >= MAX_ACTIVE_SUBAGENTS_HARD_CAP:
log.warning("Rejected subagent due to hard active child cap: root=%s desc=%s", root_task_id, desc[:100])
detail = (
"Subagent rejected: hard active child limit "
f"({MAX_ACTIVE_SUBAGENTS_HARD_CAP}) exceeded for root_task_id={root_task_id}."
)
_reject_schedule_task(
ctx, tid=tid, chat_id=chat_id, delegation_role=delegation_role,
parent_id=parent_id, root_task_id=root_task_id, role=role,
result_fields=result_fields, detail=detail,
)
return
queued_behind_active_cap = True
_record_delegation_constraint(
root_task_id,
task_id=tid,
role=role,
directive="cap_children",
scope={"max_children": max_active},
rationale=f"Queued behind active subagent cap {max_active}; wait for a slot before additional fan-out.",
advisory=True,
)
# Assignment, not admission, proves achieved depth.
admitted_task_contract, admitted_depth_provenance = stamp_depth_provenance(
task_contract,
attempted_depth=depth,
max_depth=max_depth,
achieved_depth=None,
)
text = _compose_subagent_text(
desc,
role=role,
expected_output=expected_output,
constraints=constraints,
context=task_context,
task_constraint=task_constraint,
delegation_budget=admitted_task_contract.get("delegation_budget") if isinstance(admitted_task_contract, dict) else None,
) if delegation_role == "subagent" else desc
task = _build_scheduled_task_payload({
"tid": tid,
"chat_id": chat_id,
"text": text,
"desc": desc,
"expected_output": expected_output,
"constraints": constraints,
"role": role,
"task_context": task_context,
"depth": depth,
"root_task_id": root_task_id,
"session_id": session_id,
"actor_id": actor_id,
"delegation_role": delegation_role,
"memory_mode": memory_mode,
"drive_root": drive_root,
"child_drive_root": child_drive_root,
"budget_drive_root": budget_drive_root,
"root_cost_ceiling_usd": root_cost_ceiling_usd,
"task_constraint": task_constraint,
"workspace_root": workspace_root,
"workspace_mode": workspace_mode,
"parent_workspace": evt.get("parent_workspace"),
**({"workspace_copy": workspace_copy} if workspace_copy else {}),
**{key: evt[key] for key in ("directory_strategy", "scope_paths") if key in evt},
"project_id": project_id,
"allowed_resources": allowed_resources,
"task_contract": admitted_task_contract,
"depth_provenance": admitted_depth_provenance,
"required_capabilities": required_capabilities,
"model_lane": requested_model_lane,
**intent,
"task_group_id": task_group_id,
"task_group": task_group,
"subagent_envelope": subagent_envelope,
"configured_subagent": configured_subagent,
"parent_cognitive_route": parent_cognitive_route,
"parent_id": parent_id,
"origin_metadata": evt.get("origin_metadata"),
**({"presence_binding_authority": evt["presence_binding_authority"]}
if "presence_binding_authority" in evt else {}),
})
if project_basis is not None:
task["_project_admission"] = project_basis
scheduled_failure_reason = ""
scheduled_failure_detail = ""
persist_scheduled_failure = False
if delegation_role == "subagent":
from supervisor.task_admission import enqueue_subagent_with_scheduled_result
(
admitted,
scheduled_failure_reason,
scheduled_failure_detail,
persist_scheduled_failure,
) = enqueue_subagent_with_scheduled_result(
ctx,
task,
result_fields=result_fields,
admitted_task_contract=admitted_task_contract,
admitted_depth_provenance=admitted_depth_provenance,
direct_child_count=direct_child_count,
pending_ref=pending_ref,
)
else:
admitted = ctx.enqueue_task(task)
if isinstance(admitted, dict) and admitted.get("_admission_blocked"):
from supervisor.task_admission import scheduled_admission_rejection
_reject_schedule_task(
ctx,
tid=tid,
chat_id=chat_id,
delegation_role=delegation_role,
parent_id=parent_id,
root_task_id=root_task_id,
role=role,
result_fields=result_fields,
**scheduled_admission_rejection(
admitted, project_id=project_id, root_task_id=root_task_id,
),
)
return
if isinstance(admitted, dict) and admitted.get("_admission_uncertain"):
ctx.persist_queue_snapshot(reason="schedule_subagent_receipt_unconfirmed")
log.warning("%s: %s", tid, admitted["_admission_uncertain"])
return
if scheduled_failure_reason:
if scheduled_failure_reason == "scheduled_event_replay":
return
result_fields["delegation_admission"] = {
"status": "rejected",
"reason_code": scheduled_failure_reason,
"direct_child_count": direct_child_count,
}
_reject_schedule_task(
ctx,
tid=tid,
chat_id=chat_id,
delegation_role=delegation_role,
parent_id=parent_id,
root_task_id=root_task_id,
role=role,
result_fields=result_fields,
detail=scheduled_failure_detail,
reason_code=scheduled_failure_reason,
persist_result=persist_scheduled_failure,
)
ctx.persist_queue_snapshot(reason="schedule_subagent_receipt_rollback")
return
if delegation_role != "subagent":
result_fields["task_contract"] = admitted_task_contract
result_fields["depth_provenance"] = admitted_depth_provenance
try:
write_task_result(
ctx.DRIVE_ROOT,
tid,
STATUS_SCHEDULED,
**result_fields,
result="Task accepted and scheduled.",
)
except Exception:
log.warning("Failed to persist scheduled task status for %s", tid, exc_info=True)
progress_meta = {
"root_task_id": root_task_id,
"parent_task_id": parent_id,
"delegation_role": delegation_role,
"task_group_id": task_group_id,
"required_capabilities": required_capabilities,
"requested_model_lane": requested_model_lane,
# v6.82 (P5): host-attested cancelability, ROOTS ONLY. Every task
# admitted here is a supervisor-queue task the cancel endpoint can
# reach, but the marker exists to gate the ROOT card's "Cancel run"
# button — a subagent row must never carry it (its card is a child
# card, and a lineage-less replay of a marked child row could mint a
# root-shaped card with a live Cancel). Direct-chat turns never pass
# through this path (or RUNNING).
"cancelable": delegation_role != "subagent",
}
if delegation_role == "subagent":
progress_meta.update(_subagent_scheduled_meta(
tid=tid, role=role, task_constraint=task_constraint,
task_group_id=task_group_id, requested_model_lane=requested_model_lane,
active_subagent_count=_active_subagent_count(root_task_id, pending_ref, running_ref),
max_active_subagents=max_active,
))
if queued_behind_active_cap:
progress_meta["queued_behind_active_cap"] = True
else:
progress_meta["task_event"] = "scheduled"
workers = getattr(ctx, "WORKERS", {}) or {}
if workers and not any(not getattr(worker, "busy_task_id", None) for worker in workers.values()):
progress_meta["worker_saturation_warning"] = True
suffix = " (all workers are currently busy; it will start when one is free)"
else:
suffix = ""
if delegation_role == "subagent" and queued_behind_active_cap:
suffix = (
f" (queued behind active subagent cap {max_active}; it will start when a slot frees)"
)
# A subagent's scheduled notice routes to its root project thread by lineage (C4.4); else its own chat, the hidden partition included (membership, not truthiness).
_notice_chat = notification_chat_route(
(_bound_project_chat_id(ctx, tid, parent_id, root_task_id) or None)
if delegation_role == "subagent" else None,
chat_id,
)
# A LIVE toast needs a chat a human reads. The hidden partition has none,
# and a headless run's progress log is a benchmark trajectory input, so a
# host toast there would be published as the agent's own narration. The
# durable record still keeps the true address (row_chat_identity).
if _notice_chat is not None and _notice_chat != HIDDEN_CHAT_ID:
ctx.send_with_budget(
_notice_chat,
f"🗓️ Scheduled subagent {tid}{' (' + role + ')' if role else ''}: {desc}{suffix}" if delegation_role == "subagent" else f"🗓️ Scheduled task {tid}: {desc}",
is_progress=True, task_id=tid, progress_meta=progress_meta,
role="system", system_type="task_scheduled")
ctx.persist_queue_snapshot(reason="schedule_subagent_event")